Beginner: Trying to understand how apps interact in Django
Asked Answered
N

4

29

I just got done working through the Django tutorials for the second time, and am understanding things much more clearly now. However, I'm still unclear how apps inside a site interact with one another.

For example, lets say I'm writing a blog application (a rather popular activity, apparently). Blog posts and comments tend to go together, and yet they are distinct enough that they should be built into separate apps, as is the general philosophy of Djano development.

Consider the following example. In reality I would not actually write the comment app myself, as good code for that already exists on the web, but this is for demonstration/practice purposes:

mysite/blog/models.py

from django.db import models

class post(models.Model):
    title = models.CharField(max_length=200)
    author = models.CharField(max_length=200)
    content = models.TextField()

mysite/comments/models.py

from django.db import models
from mysite.blog.models import post

class comment(models.Model):
    id = models.AutoField()
    post = models.ForeignKey(post)
    author = models.CharField(max_length=200)
    text = models.TextField()

Is what I wrote above, importing a model from another app and setting it as a foreign key, how Django apps interact? Or is there a different/better method for the apps that comprise a site to interact?

Update
Per the recommendation in one response, I'm reading the documentation for contrib.contenttypes. If I'm reading this correctly, I could rewrite my example comment app like this:

from django.db import models  
from django.contrib.contenttypes.models import ContentType
from django.contrib.contentypes import generic

class comment(models.Model):  
    id = models.AutoField()  
    author = models.CharField(max_length=200)  
    text = models.TextField()  
    content_type = models.ForeignKey(ContentType)  
    content_object = generic.GenericForeignKey(content_type, id)  

Would this be correct?

Narco answered 9/12, 2008 at 17:25 Comment(2)
Did I write my update correctly?Narco
No. There's no reason to have an AutoField in your comment model. You should have a field instead like "object_id=models.IntegerField()" and use that in your GenericForeignKey.Cachou
C
22

Take a look at django's built-in contenttypes framework:

django.contrib.contenttypes

It allows you develop your applications as stand-alone units. This is what the django developers used to allow django's built-in comment framework to attach a comment to any model in your project.

For instance, if you have some content object that you want to "attach" to other content objects of different types, like allowing each user to leave a "favorite" star on a blog post, image, or user profile, you can create a Favorite model with a generic relation field like so:

from django.db import models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic

class Favorite(models.Model):
    user = models.ForeignKey(User)
    content_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    content_object = generic.GenericForeignKey('content_type', 'object_id')

In this way you can add a Favorite star from any user to any model in your project. If you want to add API access via the recipient model class you can either add a reverse generic relation field on the recipient model (although this would be "coupling" the two models, which you said you wanted to avoid), or do the lookup through the Favorite model with the content_type and object_id of the recipient instance, see the official docs for an example.

Childbirth answered 9/12, 2008 at 17:25 Comment(1)
What about if I had both the blog app, and a "bookmarks" app, and wanted to use the generic comments app to let users post comments on both? Is there a way to do this, or would I have to write wrapper apps that inherited the comment model and overwrote comment.post?Narco
A
4

"Is what I wrote above, importing a model from another app and setting it as a foreign key, how Django apps interact?"

Yep. Works for me.

We have about 10 applications that borrow back and forth among themselves.

This leads to a kind of dependency in our unit test script.

It looks like this.

  • "ownership". We have a simple data ownership application that defines some core ownership concepts that other applications depend on. There are a few simple tables here.

  • "thing". [Not the real name]. Our thing application has data elements owned by different user groups. There are actually several complex tables the model for this app. It depends on "ownership".

  • "tables". [Not the real name]. Some of our users create fairly complex off-line models (probably with spreadsheets) and upload the results of that modeling in "tables". This has a cluster of fairly complex tables. It depends on "ownership".

  • "result". [Not the real name]. Our results are based on things which have owners. The results are based on things and tables, and are responses to customer requests. This isn't too complex, perhaps only two or three core tables. It depends on "things" and "table". No, it doesn't completely stand-alone. However, it is subject to more change than the other things on which it depends. That's why it's separate.

  • "processing". We schedule and monitor big batch jobs. This is in this application. It's really generic, and can be used in a variety of ways. It completely stands alone.

  • "welcome". We have a "welcome" app that presents a bunch of mostly static pages. This doesn't have too many tables. But it's on it's second incarnation because the first was too complex. It completely stands alone.

The only relationship among the dependent apps is some table names. As long as we preserve those tables (and their keys) we can rearrange other apps as we see fit.

Ardoin answered 9/12, 2008 at 17:25 Comment(0)
F
3

There's nothing wrong (imho) with making some app dependent on another. After all, apps are just operations on a set of models. you just have to always be aware of which app depends on which app (I guess you could call that a dependency map).

You can achieve loose coupling with the contenttypes framework. It allows an app to be truely portable/pluggable yet still integrated with other applications.

I wrote a comments app (yea, I re-invented the wheel), that can be integrated into any other application, with a few lines in the template of the page where comments should be posted (using custom tags).

Say you want a model "thread" to be pluggable into any other model. The idea is to create e generic foreign key (see django documentation on that), and write a small function that takes any object and returns a "thread" corresponding to it (or creates one if necessary), and write a custom template tag that uses that functionality, e.g. {% get_thread for arbitrary_object as thread %}. All posts are related to a thread, which is related to the object, which can be of any type.

You can think of the "thread" object as a kind of a proxy, so instead of having a post be related to a certain "article" or a "blog post", it's just related to a thread, which is abstract in a sense, what is is a thread? It's just a collection of posts. The thread then allows itself to be related to any object regardless of its type. (although it does more than that, it could hold extra information such as allowing/disallowing anon. posts, closing/opening comments on the page, etc ..)

EDIT

Here's how you can create a generic foreign key with the content types framework:

from django.contrib.contenttypes import generic
from django.contrib.contenttypes.models import ContentType

class Thread( models.Model ):
    object_type = models.ForeignKey(ContentType)
    object_id = models.PositiveIntegerField()
    object = generic.GenericForeignKey('object_type', 'object_id')

You can make it more "transparent" by exploiting the implicit "common" interface that django assumes all objects implement ..

    #inside the Thread class:
    def __unicode__(self):
        return unicode(self.object)
    def get_absolute_url(self):
        return self.object.get_absolute_url()
Frightfully answered 9/12, 2008 at 17:25 Comment(0)
S
2

Your code seems correct. I would keep the post and the comment in a blog app though. I am not saying this is the Django way, but those models are close enough to be in the same app.

How To Divide The Project

I would seperate an app if;

  • I plan to design it resuable. (and try loose coupling)
  • (for big projects) It consists of a major section of the project.

On the other hand; having many tiny apps (such as an app with a single model and two views) is hard to read and maintain IMHO.

How Apps Should Interact

This depends on the type of project and the type of the app again. For example if an app is implicitly dependent on another (ie not generic) importing and using references from the other app is acceptable. In this case the second app might be installed alone, but the first one needs the presence of the second.

If you want to make an app highly reusable and generic, such as a commenting app, you might need to integrate some setup mechanism. Maybe some new settings or additional URL configuration, or a special directive/method on your models... django.contrib.admin is a good example for this.

Apps shouldn't interact if it is not necessary though. Designing apps to avoid unnecessary coupling is very useful. It improves your app's flexibility and makes it more maintainable (but possibly with a higher cost in integrating).

Stoppage answered 9/12, 2008 at 17:25 Comment(2)
Thanks for that explanation. In this context, I would want both post and comment to be independent, as they could both be used elsewhere. Could you explain what you mean by integrating a setup mechanism?Narco
crashsystems is right, the comments should be in a separate application, because comments can be left on lots of things: blog posts, photos, videos, etc.Appertain

© 2022 - 2024 — McMap. All rights reserved.