App Engine模型,关于主键和复合键的get_or_insert问题

1 投票
2 回答
1422 浏览
提问于 2025-04-16 14:28

A] 问题总结:

我有多个模型之间的层级关系。

国家 (1) --> 城市 (多个)
城市 (1) --> 状态 (多个)

也就是说,只有一个独特的国家,一个国家只能有一个独特的城市,而一个城市可以有多个状态。

我打算使用“get_or_insert”方法来确保在数据库中保持唯一的记录。

B] 代码片段:

1] 模型结构 --

class UserReportedCountry(db.Model):
  name = db.StringProperty(required=True)

class UserReportedCity(db.Model):
  country = db.ReferenceProperty(UserReportedCountry, collection_name='cities')
  name = db.StringProperty(required=True)   

class UserReportedStatus(db.Model):
  city = db.ReferenceProperty(UserReportedCity, collection_name='statuses')
  status = db.BooleanProperty()
  date_time = db.DateTimeProperty(auto_now_add=True)

2] 用于存储从HTML表单获取的数据的代码:

def store_user_data(self): 
  country_name = self.request.get('selCountry')
  user_reported_country = UserReportedCountry.get_or_insert(name=country_name)

  user_reported_city =  UserReportedCity.get_or_insert( name = self.request.get('city'), country = user_reported_country )

  user_reported_status = UserReportedStatus( status = self.request.get('status'), city = user_reported_city)
    user_reported_status.put()      

问题:

1] 从谷歌搜索来看,“get_or_insert”需要一个键。在我的情况下,在“UserReportedCountry”模型中,我希望国家的名称作为主键,而在“UserReportedCity”模型中,我希望国家名称和城市名称的组合作为键。我该怎么做呢?

2] 有没有办法在不指定键的情况下使用“get_or_insert”?我在stackoverflow上看到过一个帖子(http://stackoverflow.com/questions/4308002/google-app-engine-datastore-get-or-insert-key-name-confusion),我尝试了那个想法,但没有成功。

谢谢你的阅读,

[编辑#1]

根据@Josh Smeaton的回复,做了一些更改的总结:

1] 现在代码会检查用户报告的国家是否存在于数据库中。如果用户报告的国家不存在,代码就会创建一个UserReportedCountry、UserReportedCity,并附加一个新的状态。

2] 如果国家存在,代码会检查给定国家下用户报告的城市是否存在。

如果找不到城市,就创建一个城市记录,并将其与找到的国家关联,并附加一个状态记录。

如果找到了城市,就将状态记录附加到它上面。

请求:

如果有人能帮我做一下代码审查,告诉我是否有错误,我将非常感激。

谢谢,

代码片段:

#this method will be used to parse the data the user provided in the html form and store it in the database models
#while maintaing the relationship between UserReportedCountry, UserReportedCity and UserReportedStatus
#BUG, there needs to be error checking to make sure the country , city and status data is invalid or not
#if the data is invalid, then error message needs to be reported and then redirection back to the main page
def store_user_data(self):
    #method call to find out the completly filled out UserReportedCity model
    user_reported_city = self.find_or_create_user_reported_country_and_city(
                                self.request.get('selCountry'), self.request.get('city'))

    #status is always unique for a user entry, so create a brand new UserReportedStatus everytime.
    user_reported_status = UserReportedStatus(status = self.get_user_reported_status(), city = user_reported_city)
    user_reported_status.put()            

#Here the code needs to find out if there is an existing country/city for the user selection
#1] If the user reported country doesnt exist, create a new country record, create a new city record and return the city record
#2] If the user reported country exists, check if the user reported city is associated with the country. 
#if the city exists, then return it. If the city doesnt exists, then create a new city and return it  
#example: if the user chooses USA, there needs to be a check if USA is already present or not, 
#so that we dont create an additonal USA record
def find_or_create_user_reported_country_and_city(self, country_name, city_name):
    country_query_result = db.GqlQuery("SELECT * FROM UserReportedCountry WHERE name = :country_name_value" 
                                       ,country_name_value = country_name).get()

    if (country_query_result == None):
        #since the country doesnt exists, create and save the country
        user_reported_country = self.create_and_save_user_country_record(country_name)

        #Since the country doesnt exist, there cannot be a city record for the given country, so blindly create the record
        return self.create_and_save_user_city_record(city_name, user_reported_country)
    else:
        #Since we found a country, now we need to find whether the user selected city exists for the given country
        return self.find_or_create_city_for_country(country_query_result, city_name)

#Check wheter the user selectred city exists in the country
#1] if the city exists return the record back 
#2] if the city doesnt exist creaty the city record and return it   
def find_or_create_city_for_country(self, country_record, city_name):
    city_query_result = db.GqlQuery("SELECT * FROM UserReportedCity WHERE name = :city_name_value AND country =:country_value"
                                     ,city_name_value = city_name, country_value = country_record ).get()

    if (city_query_result == None):
        #Since the city doesnt exist for the given country, 
        #create the city record, associated it with the country and return the record back
        return self.create_and_save_user_city_record(city_name, country_record)
    else:
        #since the city was found, return the record back 
        return city_query_result    

#method to create a UserReportedCountry record for a given country name 
def create_and_save_user_country_record(self, country_name):
    user_reported_country = UserReportedCountry(name= country_name)
    user_reported_country.put()
    return user_reported_country

#method to create a UserReportedCity record for a given city name and a given country record
def create_and_save_user_city_record (self, city_name, country_record):
    user_reported_city = UserReportedCity(name = city_name, country = country_record)
    user_reported_city.put()
    return user_reported_city

[编辑#2]

在HTML表单中,保存数据的调用是通过“post”完成的。你觉得这仍然是个问题吗?

<div id="userDataForm">
    <form method="post" action="/UserReporting">
      <p> Select Country: </p>
      <select name="selCountry" id="country">
      <!-- By default, we will select users country -->
      <script type="text/javascript" language="JavaScript">
            document.write("<option value=\"" + geoip_country_name() + "\" selected>"
      </script>
      :
      :
      :
      <p> Select City: </p>
      <div>
        <input type="text" name="city" id="city"> 

        <!-- By default, we will select users city -->
        <script type="text/javascript" language="JavaScript">
            document.getElementById("city").value = geoip_city()
        </script>

      </div>    

      <input type="submit" name="report_down" value="Report Down">
      <input type="submit" name="report_up" value="Report Up"> 
    </form>
<div>           

最开始我尝试使用Django表单,但因为不知道如何用JavaScript在Django表单中选择一个值而被阻塞。

2 个回答

1

我不确定GAE是否使用了内部的Meta类,但在Django中,我会在国家定义中使用unique字段参数来确保国家名称是唯一的,同时在城市定义中使用unique_together元组来确保`('country', 'name')`的组合是唯一的。这样做可以保证数据的完整性,避免你在使用get_or_insert时忘记正确的操作。

另外,你可以先查找名称(也就是执行获取操作),如果这个名称还不存在,就进行插入。基本上,就是在自己的代码中模仿get_or_insert的功能。

3

针对你的问题,我来逐一解答:

1] 从谷歌搜索来看,"get_or_insert" 需要一个键。在我的例子中,在 "UserReportedCountry" 模型里,我想把国家的名字作为主键,而在 "UserReportedCity" 模型里,我想把国家名字和城市名字的组合作为键。我该怎么做呢?

你只需要指定国家的名字,以及国家和城市的组合(比如 "USA/San Francisco")作为你传给 get_or_insert 的键名。顺便说一下,get_or_insert 其实就是一种语法上的简化,下面的代码也能实现同样的功能:

def get_or_insert(cls, key_name, **kwargs):
  def _tx():
    obj = cls.get_by_key_name(key_name)
    if obj is None:
      return cls(key_name, **kwargs)
    else:
      return obj
  return db.run_in_transaction(_tx)

2] 有没有办法使用 "get_or_insert" 而不指定键呢?我在 StackOverflow 上看到以下帖子(http://stackoverflow.com/questions/4308002/google-app-engine-datastore-get-or-insert-key-name-confusion),尝试了这个想法,但没有成功。

这样做其实没有太大意义。因为在 App Engine 中,键是模型唯一的字段,而你不能在 App Engine 中进行跨实体组的查询,所以如果不指定键,就无法进行事务性的获取或插入操作。不过,考虑到你的需求,使用国家名字和城市名字作为键名应该是没问题的。

撰写回答