views:

897

answers:

4

Hi guys,

I'm trying to get my models related using ReferenceProperty, but not have a huge amount of luck. I have 3 levels: Group, Topic, then Pros, and Cons. As in a Group houses many topics, and within each topic could be many Pros and Cons.

I am able to store new Groups nice and fine, but I don't have any idea how to store topics underneath these groups. I want to link from a page with a link "New topic" underneath each group, that takes them to a simple form (1 field for now). Obviously the URL will need to have some sort of reference to the id of the group or something.

Here are my models:

class Groups(db.Model):

    group_user = db.UserProperty()
    group_name = db.StringProperty(multiline=True)
    group_date = db.DateTimeProperty(auto_now_add=True)


class Topics(db.Model):

    topic_user = db.UserProperty()
    topic_name = db.StringProperty(multiline=True)
    topic_date = db.DateTimeProperty(auto_now_add=True)
    topic_group = db.ReferenceProperty(Groups, collection_name='topics')

class Pro(db.Model):

    pro_user = db.UserProperty()
    pro_content = db.StringProperty(multiline=True)
    pro_date = db.IntegerProperty(default=0)
    pro_topic = db.ReferenceProperty(Topics, collection_name='pros') 

class Con(db.Model):

    con_user = db.UserProperty()
    con_content = db.StringProperty(multiline=True)
    con_date = db.IntegerProperty(default=0)
    con_topic = db.ReferenceProperty(Topics, collection_name='cons')

And one function for the actual page I want to show the list of Groups, and then underneath their topics:

class Summary(webapp.RequestHandler):
    def get(self):

     groups_query = Groups.all()
     groups = groups_query.fetch(1000)
     template_values = {

      'groups': groups,   
     }

     path = os.path.join(os.path.dirname(__file__), 'summary.html')
     self.response.out.write(template.render(path, template_values))

And finally the html:

<html>
  <body>
    <a href="/newgroup">New Group</a>
    <br>
    {% for group in groups %}

    <font size="24">{{ group.group_name|escape }}</font><br> by <b>{{ group.group_user }}</b> at <b>{{ group.group_date }}</b> {{ group.raw_id }}
    <br>
    <a href="/newtopic?id={{group.key.id}}" >New topice </a>
    <br>
    <blockquote>
     {{ topics.topics_name }}
    </blockquote>


    {% endfor %}
  </body>
</html>
+2  A: 

Something that has side effects, such as altering the store (by creating a new object for example) should NOT be an HTTP GET -- GET should essentially only do "read" operations. This isn't pedantry, it's a key bit of HTTP semantics -- browsers, caches, proxies, etc, are allowed to act on GET as read-only operations (for example by caching results and not passing a request to the server if they can satisfy it from cache).

For modifications, use HTTP verbs such as POST (most popular essentially because all browsers implement it correctly) or for specialized operations PUT (to create new objects) or DELETE (to remove objects). I assume you'll be going to use POST to support a variety of browsers.

To get a POST from a browser, you need either Javascript wizardy or a plain old form with method=post -- I'll assume the latter for simplicity.

If you're using Django 1.0 (which app engine supports now), it has its own mechanisms to make, validate and accept forms based on models. Other frameworks have their own similarly advanced layers.

If you want to avoid "rich" frameworks you'll have to implement by hand templates for your HTML forms, direct them (via some kind of URL dispatching, e.g. in app.yaml) to a handler of yours implementing with a def post(self):, get the data from the request, validate it, form the new object, put it, display some acknowledgment page.

What part or parts of the procedure are unclear to you? Your question's title focuses specifically on reference properties but I'm not sure what problem they are giving you in particular -- from the text of your question you appear to be on the right tack about them.

Edit: the OP has now clarified in a comment that his problem is how to make something like:

"<a href="/newtopic?id={{group.key.id}}" >New topic </a>"

work. There's more than one way to do that. If the newtopic URL is served by a static form, the handler for the post "action" of that form could get back to that id= via the Referer: header (a notorious but unfixable mis-spelling), but that's a bit clunky and fragile. Better is to have the newtopic URI served by a handler whose def get gets the id= from the request and inserts it in the resulting form template -- for example, in a hidden input field. Have that form's template contain (among the other fields):

<INPUT TYPE=hidden NAME=thegroupid VALUE={{ theid }}> </INPUT>

put theid in the context with which you render that template, and it will be in the request that the def post of the action receiving the form finally gets.

Alex Martelli
What? I don't see him doing this anywhere, or asking about doing it either. Did you mean to post this on a different question?
Nick Johnson
@Nick, the question was unclear -- just in case that `<a href=` was intended to lead directly to topic creation, I warned against that (now the OP clarified it properly leads to a posted form). You had trouble understanding the question too (though at least your answer did elicit some clarification, it seems), so why downvote a broad spectrum answer that tries to make sure all the bases are touched in the face of ambiguity?
Alex Martelli
Hmm a hidden form item - neat solution. I'm still not understanding how one uses the datastore correctly though. So lets say I have the id of the group, and I parse it into NewTopicSubmit (you can see it above - same form as NewGroupSubmit) class; how do you set the relationship? I need to use something like topic.topic_group =.... id? Not grasping this and feel like an idiot ><
Dominic Bou-Samra
A reference value is the *key* of the referred-to entity; once you have the group object grp, `newtopic.topic_group = grp.key()` is what needs to happen before the `newtopic.put()`, though you can just do `newtopic.topic_group = grp` and it will take the key on your behalf. So best might be to pass along the key directly ("A string-encoded key is an opaque value using characters safe for including in URLs... can be converted back to a Key object by passing it to the Key constructor (the encoded argument)." as per http://code.google.com/appengine/docs/python/datastore/keyclass.html
Alex Martelli
...though of course Key.from_path lets you build the key from the entity kind, parent[s] if any, and ID.
Alex Martelli
I downvoted it because it seemed like a rather strident telling off for something that wasn't even apparrent from the question. I, at least, assumed that that link ought to lead to a form for creating a new topic, not actually do the insertion. Regardless, I removed my downvote.
Nick Johnson
Thank you both! I knew it was ridiculously simple, and blatently obvious so it feels good to have solved it :D I used get_by_id(id) and it works beautifully. Ty for sticking with it
Dominic Bou-Samra
A: 

Thankyou for the reply.

Yeah I am aware of the get vs post. The class I posted was just to actually print all the Groups().

The issue I have is I'm unsure how I use the models to keep data in a sort of hierarchical fashion, with Groups > Topics > Pros/Cons.

Grabbing data is simple enough and I am using:

class NewGroupSubmit(webapp.RequestHandler):
    def post(self):

     group = Groups()
     if users.get_current_user():
      group.group_user = users.get_current_user()  
     group.group_name = self.request.get('groupname')

     group.put()
     self.redirect('/summary')

I need another function to add a new topic, that stores it within that group. So lets say a group is "Cars" for instance; the topics might be "Ferrari", "Porsche", "BMW", and then pros/cons for each topic. I realise I'm being a little vague, but it's because I'm very new to relational databasing and not quite used to the terminology.

Dominic Bou-Samra
A: 

I'm not quite sure what problem you're having. Everything you list looks fine - the ReferenceProperties are set up according to what one would expect from your dscription. The only problem I can see is that in your template, you're referring to a variable "topics", which isn't defined anywhere, and you're not iterating through the topics for a group anywhere. You can do that like this:

<html>
  <body>
    <a href="/newgroup">New Group</a>
    <br>
    {% for group in groups %}

    <font size="24">{{ group.group_name|escape }}</font><br> by <b>{{ group.group_user }}</b> at <b>{{ group.group_date }}</b> {{ group.raw_id }}
    <br>
    <a href="/newtopic?id={{group.key.id}}" >New topice </a>
    <br>
    Topics:
    <ul>
      {% for topic in group.topics %}
        <li>{{topic.topic_name}}</li>
      {% endfor %}
    </ul>
    {% endfor %}
  </body>
</html>

To create a new topic, just use the constructor, passing in the required arguments:

mytopic = Topic(topic_name="foo", topic_group=somegroup)

Here, somegroup should be either a Group object, or a key for a Group object.

Nick Johnson
Yeah I know I can do that, but I am unsure how what my post function for a new topic would be. How do I write to datastore so that topics have a group. You can see my attempts with: "<a href="/newtopic?id={{group.key.id}}" >New topice </a>" but I don't think that has anything to do with it, because I obviously need to pass that into the function somehow.
Dominic Bou-Samra
Does this update help at all? You really need to clarify - in your original question - exactly what your question is, though.
Nick Johnson
A: 

Just to answer the question for others as you probably figured this out:

class NewTopic(webapp.RequestHandler):
    def get(self):
      groupId = self.request.get('group')
      # either get the actual group object from the DB and initialize topic with topic_group=object as in 'Nick Johnson's answer, or do as follows
      topic = Topic()
      topic.name = self.request.get("topicname")
      topic.reference = groupId
      topic.put()
Keith