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

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

Related

Add a virtual field to a django query

I want to add an extra field to a query set in Django.
The field does not exist in the model but I want to add it to the query set.
Basically I want to add an extra field called "upcoming" which should return "True"
I already tried adding a #property method to my model class. This does not work because apparently django queries access the DB directly.
models.py
class upcomingActivity(models.Model):
title = models.CharField (max_length=150)
address = models.CharField (max_length=150)
Views.py
def get(self, request):
query = upcomingActivity.objects.all()
feature_collection = serialize('geojson', query ,
geometry_field='location',
fields= ( 'upcoming','title','address','pk' )
)
This answer is for the case that you do not want to add a virtual property to the model (the model remains as is).
To add an additional field to the queryset result objects:
from django.db.models import BooleanField, Value
upcomingActivity.objects.annotate(upcoming=Value(True, output_field=BooleanField())).all()
Each object in the resulting queryset will have the attribute upcoming with the boolean value True.
(Performance should be nice because this is easy work for the DB, and Django/Python does not need to do much additional work.)
EDIT after comment by Juan Carlos:
The Django serializer is for serializing model objects and thus will not serialize any non-model fields by default (because basically, the serializer in this case is for loading/dumping DB data).
See https://docs.djangoproject.com/en/2.2/topics/serialization/
Django’s serialization framework provides a mechanism for “translating” Django models into other formats.
See also: Django - How can you include annotated results in a serialized QuerySet?
From my own experience:
In most cases, we are using json.dumps for serialization in views and this works like a charm. You can prepare the data very flexibly for whatever needs arize, either by annotations or by processing the data (list comprehension etc.) before dumping it via json.
Another possibility in your situation could be to customize the serializer or the input to the serializer after fetching the data from the DB.
You can use a class function to return the upcoming value like this:
def upcoming(self):
is_upcoming = # some logic query or just basically set it to true.
return is_upcoming
then call it normally in your serializer the way you did it.
fields= ( 'upcoming','title','address','pk' )

How django validates and saves inlineforms

I would like to implement my own functionality similar to Django inlineformsets. What I'm interested in is how Django deals with validation and saving of a main object together with it's related objects in inline forms.
Let's say I have two models: Blog and Entry. Entry has a foreign key to the Blog that is not null. I want to be able to create both the blog and it's entries in one place. This is how I would do it using Django inline forms:
blogform = BlogForm(request.POST)
if blogform.is_valid():
tmp = blogform.save(commit=False)
entriesform = EntryInlineFormset(request.POST, instance=tmp)
if entriesform.is_valid():
entriesform.save()
blog.save()
What's going under the hood here? How is Django able to validate entries without blog being saved to the database? I wanted to find this in Django code, but I wasn't able to find the place where they actually do this.
My gues is they create a transaction. They save the blogform and if the entriesform is invalid they rollback. However what if the entriesform is valid, what next? Does the blog instance stay saved? What if save never gets called then.
Or do they span the transaction over two methods (is_valid and save)? I don't think it's best practice to start the transaction in one method and end it in another.
You can validate them both before calling save on either. You can pass a blank instance into both the parent form and the formset.
blog = Blog()
blogform = BlogForm(request.POST, instance=blog)
entriesform = EntryInlineFormset(request.POST, instance=blog)
blog_valid = BlogForm.is_valid()
entries_valid = entriesform.is_valid()
if blog_valid and entries_valid:
... save ...
I validate the forms separately and save the results to variables to avoid short-circuiting.

Tastypie Dehydrate reverse relation count

I have a simple model which includes a product and category table. The Product model has a foreign key Category.
When I make a tastypie API call that returns a list of categories /api/vi/categories/
I would like to add a field that determines the "product count" / the number of products that have a giving category. The result would be something like:
category_objects[
{
id: 53
name: Laptops
product_count: 7
},
...
]
The following code is working but the hit on my DB is heavy
def dehydrate(self, bundle):
category = Category.objects.get(pk=bundle.obj.id)
products = Product.objects.filter(category=category)
bundle.data['product_count'] = products.count()
return bundle
Is there a more efficient way to build this query? Perhaps with annotate ?
You can use prefetch_related method of QuerSet to reverse select_related.
Asper documentation,
prefetch_related(*lookups)
Returns a QuerySet that will automatically
retrieve, in a single batch, related objects for each of the specified
lookups.
This has a similar purpose to select_related, in that both are
designed to stop the deluge of database queries that is caused by
accessing related objects, but the strategy is quite different.
If you change your dehydrate function to following then database will be hit single time.
def dehydrate(self, bundle):
category = Category.objects.prefetch_related("product_set").get(pk=bundle.obj.id)
bundle.data['product_count'] = category.product_set.count()
return bundle
UPDATE 1
You should not initialize queryset inside dehydrate function. queryset should be always set in Meta class only. Please have a look at following example from django-tastypie documentation.
class MyResource(ModelResource):
class Meta:
queryset = User.objects.all()
excludes = ['email', 'password', 'is_staff', 'is_superuser']
def dehydrate(self, bundle):
# If they're requesting their own record, add in their email address.
if bundle.request.user.pk == bundle.obj.pk:
# Note that there isn't an ``email`` field on the ``Resource``.
# By this time, it doesn't matter, as the built data will no
# longer be checked against the fields on the ``Resource``.
bundle.data['email'] = bundle.obj.email
return bundle
As per official django-tastypie documentation on dehydrate() function,
dehydrate
The dehydrate method takes a now fully-populated bundle.data & make
any last alterations to it. This is useful for when a piece of data
might depend on more than one field, if you want to shove in extra
data that isn’t worth having its own field or if you want to
dynamically remove things from the data to be returned.
dehydrate() is only meant to make any last alterations to bundle.data.
Your code does additional count query for each category. You're right about annotate being helpfull in this kind of a problem.
Django will include all queryset's fields in GROUP BY statement. Notice .values() and empty .group_by() serve limiting field set to required fields.
cat_to_prod_count = dict(Product.objects
.values('category_id')
.order_by()
.annotate(product_count=Count('id'))
.values_list('category_id', 'product_count'))
The above dict object is a map [category_id -> product_count].
It can be used in dehydrate method:
bundle.data['product_count'] = cat_to_prod_count[bundle.obj.id]
If that doesn't help, try to keep similar counter on category records and use singals to keep it up to date.
Note categories are usually a tree-like beings and you probably want to keep count of all subcategories as well.
In that case look at the package django-mptt.

Pass an entire inital object in an edit form (data pre-population) in Django

I have a very complicated form and I choose to not use ModelForm since I needed flexibility and control over the fields. Since I am not using ModelForm, I can't simply do something like instance=order, where order = Order.objects.get(pk=1).
Currently I am pre-populating every field with initial in the forms.py as oppose to the views.py like this
self.fields['work_type'] = forms.ChoiceField(choices=Order.WORK_TYPE_CHOICES, initial=order.work_type)
But I was wondering if I could pass the entire order object to a form or do I have to declare initial to every field?
Is there a way to do something like
order_form = OrderEditForm(data=request.POST, initial=order)
in views.py?
I have a very complicated form and I choose to not use ModelForm since
I needed flexibility and control over the fields
Everything that you can do using a Form, you can do in a ModelForm such as adding new fields or over-riding attributes on the fields etc.
But I was wondering if I could pass the entire order object to a form
or do I have to declare initial to every field?
You can pass the order object into the form but you will still have to populate each field individually either in the forms or in the view function.
So in your view you would do something like this:
intial = {'order_number': order.number, 'order_id': order.id}
form = OrderForm(initial=initial)
The easiest way to prepopulate data to a form is passing a dictionary as first argument to de form constructor.
order_form = OrderEditForm(order.__dict__())
where __dict__() is a method that passes "order" object attributes to a dictionary with each attribute's name as a key and their content as value.
An example of how to "invent" such a method could be something like:
order_initial = Order.objects.filter(pk=order.pk).values()[0]
and then construct the form with:
order_form = OrderEditForm(order_initial)
Look at this example (how they populate values at "post" time):
For future reference to other people:
I have since found out after reading SO's comments and answers that it's better to use ModelForm even if you end up explicitly defining every field manually (using something like self.fields['foo'] = forms.CharField()).
In any case, if you are trying to pass a dictionary of current values in a form then the best (built-in) way to convert a model to a dictionary is actually using model_to_dict:
from django.forms.models import model_to_dict
order = Order.objects.get(pk=1)
dictionary = model_to_dict(order)
form = OrderEditForm(dictionary)
I got the solution from this blog. I hope this will be helpful for someone.

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

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.

Categories