App engine model, get_or_insert question w.r.t primary key and composite keys
A] Summary of the problem:
I Have 1 to many hierarchical relationships between models
Country (1) --> City (Many)
City (1) --> Status (Many)So, there can only be one unique country, a country can only have one unique city, and a city could have many statuses
I am planning to use "get_or_insert" method to make sure that i maintain unique records in the database.
B] Code Excerpts:
1] Model structure --
class UserReportedCountry(db.Model):
name = db.StringProperty(required=True)
class UserReportedCity(db.Model):
country = db.ReferenceProperty(UserReportedCountry, collection_name='cities')
name = db.开发者_如何学JAVAStringProperty(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] Code that is used for storing the data retrieved from the HTML form:
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()
Questions:
1] From the google search, it appears "get_or_insert" requires a key, In my case in the "UserReportedCountry" model, i want the name of the country to be the primary key and in the "UserReportedCity" model, i want the combination of country name + city name to be the key. How do i go about doing this ?
2] Is there a way to use "get_or_insert" without specifying a key, I came accross the following posting on stackoverflow (http://stackoverflow.com/questions/4308002/google-app-engine-datastore-get-or-insert-key-name-confusion), and tried the idea but it didnt work.
Thank you for reading,
[EDIT#1]
Summary of the changes based on the response given by @Josh Smeaton
1] Now the code checks if the user reported country is present in the db or not. If the user reported country is not present, then the code creates a UserReportedCountry, UserReportedCity and attaches a new status to it
2] If the country is present, then the code checks whether the user reported city is present for the given country.
If the city is not found, then create a city record and associated it with the found country and attach a status record.
If the city is found, then attach the status record to it.
Request:
I will highly appreciate, If someone can please do a code review and let me know if i am making any mistakes.
Thanks,
Code excerpts:
#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
[EDIT#2]
Inside the html form, the call to save the data is done using "post". Do you think this is still a problem?
<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>
Initially i tried using the Djangoforms, but i got blocked because i didnt knew how to use javascript to select a value in the djangoform
To address your questions in order:
1] From the google search, it appears "get_or_insert" requires a key, In my case in the "UserReportedCountry" model, i want the name of the country to be the primary key and in the "UserReportedCity" model, i want the combination of country name + city name to be the key. How do i go about doing this ?
Simply specify the name of the country, and the concatenation of country and city (eg "USA/San Francisco" as the key names you pass to get_or_insert
. As an aside, get_or_insert
is simply syntactic sugar for the following:
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] Is there a way to use "get_or_insert" without specifying a key, I came accross the following posting on stackoverflow (http://stackoverflow.com/questions/4308002/google-app-engine-datastore-get-or-insert-key-name-confusion), and tried the idea but it didnt work.
It doesn't really make sense to do so. The key is the only unique field for a model in App Engine, and you can't do cross-entity-group queries in App Engine, so unless you specify one it's not possible to do a transactional get-or-insert operation. Given your requirements, though, using country name and city name as key names ought to work just fine.
I don't know if GAE utilizes the inner Meta class, but in django, I'd make use of the unique
field parameter for the country name for the Country definition, and unique_together
Meta tuple for `('country', 'name') in the City definition. This will enforce the integrity in any case that you happen to forget the get_or_insert correct incantation.
Otherwise, do a lookup on the name (the get operation), and if it doesn't yet exist, do the insert. Basically, mimic the get_or_insert in your own code.
精彩评论