How to agnostically link any object/Model from another Django Model? - python

I'm writing a simple CMS based on Django. Most content management systems rely on having a fixed page, on a fixed URL, using a template that has one or many editable regions. To have an editable region, you require a Page. For the system to work out which page, you require the URL.
The problem comes when you're no longer dealing with "pages" (be those FlatPages pages, or something else), but rather instances from another Model. For example if I have a Model of products, I may wish to create a detail page that has multiple editable regions within.
I could build those regions into the Model but in my case, there are several Models and is a lot of variance in how much data I want to show.
Therefore, I want to build the CMS at template level and specify what a block (an editable region) is based on the instance of "page" or the model it uses.
I've had the idea that perhaps I could dump custom template tags on the page like this:
{% block unique_object "unique placeholder name" %}
And that would find a "block" based on the two arguments passed in. An example:
<h1>{{ product_instance.name }}</h1>
{% block product_instance "detail: product short description" %}
{% block product_instance "detail: product video" %}
{% block product_instance "detail: product long description" %}
Sounds spiffy, right? Well the problem I'm running into is how do I create a "key" for a zone so I can pull the correct block out? I'll be dealing with a completely unknown object (it could be a "page" object, a URL, a model instance, anything - it could even be a boat</fg>).
Other Django micro-applications must do this. You can tag anything with django-tagging, right? I've tried to understand how that works but I'm drawing blanks.
So, firstly, am I mad? And assuming I not, and this looks like a relatively sane idea to persue, how should I go about linking an object+string to a block/editable-region?
Note: Editing will be done on-the-page so there's no real issue in letting the users edit the zones. I won't have to do any reverse-mumbo-jumbo in the admin. My eventual dream is to allow a third argument to specify what sort of content area this is (text, image, video, etc). If you have any comments on any of this, I'm happy to read them!

django-tagging uses Django's contenttypes framework. The docs do a much better job of explaining it than I can, but the simplest description of it would be "generic foreign key that can point to any other model."
This may be what you are looking for, but from your description it also sounds like you want to do something very similar to some other existing projects:
django-flatblocks ("... acts like django.contrib.flatpages but for parts of a page; like an editable help box you want show alongside the main content.")
django-better-chunks ("Think of it as flatpages for small bits of reusable content you might want to insert into your templates and manage from the admin interface.")
and so on. If these are similar then they'll make a good starting point for you.

You want a way to display some object-specific content on a generic template, given a specific object, correct?
In order to support both models and other objects, we need two intermediate models; one to handle strings, and one to handle models. We could do it with one model, but this is less performant. These models will provide the link between content and string/model.
from django.db import models
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
CONTENT_TYPE_CHOICES = (
("video", "Video"),
("text", "Text"),
("image", "Image"),
)
def _get_template(name, type):
"Returns a list of templates to load given a name and a type"
return ["%s_%s.html" % (type, name), "%s.html" % name, "%s.html" % type]
class ModelContentLink(models.Model):
key = models.CharField(max_length=255) # Or whatever you find appropriate
type = models.CharField(max_length=31, choices= CONTENT_TYPE_CHOICES)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey('content_type', 'object_id')
def get_template(self):
model_name = self.object.__class__.__name__.lower()
return _get_template(model_name, self.type)
class StringContentLink(models.Model):
key = models.CharField(max_length=255) # Or whatever length you find appropriate
type = models.CharField(max_length=31, choices= CONTENT_TYPE_CHOICES)
content = models.TextField()
def get_template(self):
return _get_template(self.content, self.type)
Now, all we need is a template tag to grab these, and then try to load the templates given by the models' get_template() method. I'm a bit pressed on time so I'll leave it at this and update it in ~1 hour. Let me know if you think this approach seems fine.

It's pretty straightforward to use the contenttypes framework to implement the lookup strategy you are describing:
class Block(models.Model):
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
object = generic.GenericForeignKey() # not actually used here, but may be handy
key = models.CharField(max_length=255)
... other fields ...
class Meta:
unique_together = ('content_type', 'object_id', 'key')
def lookup_block(object, key):
return Block.objects.get(content_type=ContentType.objects.get_for_model(object),
object_id=object.pk,
key=key)
#register.simple_tag
def block(object, key)
block = lookup_block(object, key)
... generate template content using 'block' ...
One gotcha to be aware of is that you can't use the object field in the Block.objects.get call, because it's not a real database field. You must use content_type and object_id.
I called the model Block, but if you have some cases where more than one unique (object, key) tuple maps to the same block, it may in fact be an intermediate model that itself has a ForeignKey to your actual Block model or to the appropriate model in a helper app like the ones Van Gale has mentioned.

Related

Django 'likes' - ManyToManyField vs new model

I'm implementing likes on profiles for my website and I'm not sure which would be the best practice, a ManyToManyField like so:
class MyUser(AbstractBaseUser):
...
likes = models.ManyToManyField('self', symmetrical = False, null = True)
...
or just creating a class Like, like so:
class Like(models.Model):
liker = models.ForeignKey(MyUser, related_name='liker')
liked = models.ForeignKey(MyUser, related_name='liked')
Is one of them a better choice than the other? If so, why?
thanks
The first option should be preffered. If you need some additional fields to describe the likes, you can still use through="Likes" in your ManyToManyField and define the model Likes.
Manipulating the data entries would be also somewhat more pythonic:
# returns an object collection
likes_for_me = MyUser.objects.filter(pk=1).likes
instead of:
me = MyUser.objects.filter(pk=1)
likes_for_me = Like.objects.filter(liked=me)
The second option is basically what is done internally: a new table is created, which is used to create the links between the entities.
For the first option, you let django do the job for you.
The choice is certainly more about how you want to do the requests. On the second options, you would have to query the Like models that match you model, while on the first one, you only have to request the MyUser, from which you can access the connections.
Second option is more flexible and extensible. For example, you'll probably want to track when like was created (just add Like.date_created field). Also you'll probably want to send notification to content author when content was liked. But at first like only (add Like.cancelled boolead field and wrap it with some logic...).
So I'll go with separate model.
I think the one you choose totally depends on the one you find easier to implement or better. I tend to always use the first approach, as it is more straightforward and logical, at least to me. I also disagree with Igor on that it's not flexible and extensible, you can also initiate notifications when it happens. If you are going to use the Django rest framework, then I totally suggest using the first method, as the second could be a pain.
class Post(models.Model):
like = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name='post_like')
Then in your view, you just do this.
#api_view(['GET'])
#permission_classes([IsAuthenticated])
def like(request, id):
signed_in = request.user
post = Post.objects.get(id=id)
if signed_in and post:
post.like.add(signed_in)
# For unlike, remove instead of add
return Response("Successful")
else:
return Response("Unsuccessful", status.HTTP_404_NOT_FOUND)
Then you can use the response however you like on the front end.

Unsure about what type of Django form I should use

I have an Author model with 200k instances in a MySQL database. I want to have the user search for an Author's unique ID (which is a string) and then select an ID which will then produce a table and small graphic about that author.
Do I want to use a charfield model form? Also, is there a built in search function?
I don't think Django has a builtin function for searching. You will have to use one of its extensions for this purpose (django-search or haystack).
They may seem too complicated for your case so I would go with simplier solution (and I would give up using form):
from django.views.generic import ListView
from django.db.models import Q
from .models import Author
def SearchAuthorView(ListView):
def get_queryset(self):
name = self.request.GET['author_name']
name_tokens = name.split(' ')
matched_authors = []
authors = Author.objects.all()
for author in authors:
for name_token in author.name.split(' '):
if name_token in name_tokens:
matched_authors.append(author)
break
return matched_authors
With 200k you may hit performance problems so if you do, you should use an optimized, raw MySql query.
You may also like to order your results somehow. For example give lastname's match a bigger priority than firstname's match.
Honestly I don't understand the question. You have a table called Author with 200k instances and you want to have possibility to find one of them. This can be done by simply function in views.py
def search_author(request, author_id):
author = Author.objects.get(id=author_id)
return render_to_response('your/path/to/template', {'author': author}, RequestContext(request))
Then in your template you just simply display the informations:
<div class="author">
<p>{{author.name}}</p>
<p>{{author.image}}</p>
</div>
Of course if your models.py looks like this:
class Author(models.Model):
name = models.CharField(max_number=100)
image ....

Django Threaded Commenting System

(and sorry for my english)
I am learning Python and Django. Now, my challange is developing threaded generic comment system. There is two models, Post and Comment.
-Post can be commented.
-Comment can be commented. (endless/threaded)
-Should not be a n+1 query problem in system. (No matter how many comments, should not increase the number of queries)
My current models are like this:
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
child = generic.GenericRelation(
'Comment',
content_type_field='parent_content_type',
object_id_field='parent_object_id'
)
class Comment(models.Model):
content = models.TextField()
child = generic.GenericRelation(
'self',
content_type_field='parent_content_type',
object_id_field='parent_object_id'
)
parent_content_type = models.ForeignKey(ContentType)
parent_object_id = models.PositiveIntegerField()
parent = generic.GenericForeignKey(
"parent_content_type", "parent_object_id")
Are my models right? And how can i get all comment (with hierarchy) of post, without n+1 query problem?
Note: I know mttp and other modules but I want to learn this system.
Edit: I run "Post.objects.all().prefetch_related("child").get(pk=1)" command and this gave me post and its child comment. But when I wanna get child command of child command a new query is running. I can change command to ...prefetch_related("child__child__child...")... then still a new query running for every depth of child-parent relationship. Is there anyone who has idea about resolve this problem?
If you want to get all comments on a post with a single query then it would be good to have every comment link to the asssociated post. You can use a separate link to indicate the parent comment.
Basically:
class Post(models.Model):
...
comments = models.ManyToManyField('Comment')
# link to all comments, even children of comments
class Comment(models.Model):
...
child_comments = models.ManyToManyField('Comment')
# You may find it easier to organise these into a tree
# if you use a parent_comment ForeignKey. That way the
# top level comments have no parent and can be easily spotted.
Post.objects.all().select_related('comments').get(pk=1)
The many to many in this takes a little extra work to create the association, as it uses an intermediate table. If you want a pure one to many then you need a ForeignKey on the Comment but then you are restricted to a prefetch_related instead of a select_related, which then involves an extra database hit.
This is also better in that you do not have an untyped foreign key reference (your PostitiveIntegerField).
You then need to arrange the comments into a tree structure, but that is outside the scope of your question.

How to send django models into a template with additional custom fields?

I faintly remember seeing this done before in tutorials in the past. However, im having trouble finding out exactly how in the docs currently.
Suppose we had a model called Post. This model has a field called timestamp. However, when we send this model into the template we don't care about timestamps. Instead, we want the more popular "age" (created X mins/hrs ago), which thankfully, can be deduced from the timestamp.
Instead of creating a whole new field for timestamp, and instead of using custom template tags, can we somehow add a field to a model temporarily before sending it over to our template?
Ex.
# views.py
# Is the below code right? do I need to save()?
posts = Posts.objects.filter(...).filter(...)[:X]
for post in posts:
# Post does not have an age field, we are creating one
# temporarily before sending it to the template
post.age = some_function(post.timestamp)
return render_to_response(template, {'posts' : posts}, etc...)
Thank you.
Just make it a property on the model.
class Post(Model)
#property
def age(self):
return now() - self.timestamp

Django template able to pass parameters? Getting the current user

In my template, I use a method in a model as follows...
The model is defined like this:
class Category(model.Model):
title = models.CharField('title', max_length=255)
description = models.TextField('description', blank=True)
def pub_set(self):
...
return somedata
The Template is used something like this:
{{ category.pub_set }}
I want to be able to get the current user in pub_set
I'm not 100% clear what you are asking. Inside of Category.pub_set() are you trying to access the request.user? If so, you can't do that unfortunately. There might be some sort of hack that will work, but it isn't the recommended way in Django.
You might want to check out template tags, they will allow you to pass your category and user from the template, and then do what you need to do, and return your result. Here is a link to the documentation.
http://docs.djangoproject.com/en/1.2/howto/custom-template-tags/#passing-template-variables-to-the-tag
Another approach is to do what you need to do in the view, and then save your result in the page context, where it will be available in the template.
If I had a little more insight into what you are trying to do, it might make it easier to steer you in the right direction.

Categories