How can I add extra data to a ModelMultipleChoiceField? - django

I'm making a calorie counter. The user can pick from a list of Dishes (easy with a ModelMultipleChoiceField), but they also need to specify how many servings of each Dish they had, where the serving_size is a field of my Dish model.
Basically, I want it to look like this:
The problem is that in order to display the strings "4 oz servings" and "slices", I need access to the Dish model when the field is rendered.
What's the best way to do this?
I've got 2 rough ideas so far:
Create a custom Widget that will render as a checkbox AND a number input field. When the data gets deserialized, it becomes something like this:
{"dish": <Dish model>, "servings": 2.5}
Just use the regular CheckboxSelectMultiple widget, but add the Dish model itself to the template's context. That way I can do something like this:
{% for dish in form.fountain_dishes %}
{{dish}}
I ate <input type="number"> {{dish.context.serving_size}}
{% endfor %}
However, I'm not sure how to make my clean method handle this unexpected data.

Related

Django - Field by Field Restrictions on Which Users Can view Certain Fields

I'm learning Django and building a simple CRUD application. I have a model for cars, and this contains a number of fields. When a user loads a car page, I'd like some fields to be displayed and others hidden, depending on whether the user has a high enough score for the car they are viewing. So for each field (engine, wheels, headlights, etc), or for some groups of fields, there would be a corresponding visibility score. If the user's score for that particular car exceeded the visibility for a particular field, then the data for that field would be displayed in the view.
I could add a DecimalField variable for each component to denote this minimum score, the Car model would have a calc_score(user) method. In the view the current user's score would be compared against each of these, but I'm guessing there may be a better way to do this. Can anyone recommend a better approach?
Thank you.
You should just pass in the user's score as context when rendering the page from your views.py. It would look something like this:
def car_page_view(request):
current_user_score = getUserScore()
context = {
'user_score': current_user_score
}
return render(request, 'car-page.html', context)
Then, in your html, put a check like this to only show certain elements:
{% if user_score > 10 %}
<text>This text is only visible to users with scores higher than 10.</text>
{% endif %}
This is better than, say, loading all html elements and setting certain elements to "hidden". Instead, this causes the html you want hidden to never be loaded at all. So even if they use the inspect tool they won't see it :)

Filtering a queryset across a foreign key, using a parameter e.g. as argument from the view

I'm trying to build a flexible queryset that gives a result size based on parameters I use, but currently I'm only accessing that data from across a foreign key. Here's the setup.
My first table is a set of "blog posts" - the model is pretty self explanatory:
class BlogPost(models.Model):
title = models.CharField(max_length=100)
dateStamp = models.DateTimeField(auto_now_add=True)
post = models.TextField()
The second is a table that holds all the images for all the posts in a one-to-many relationship:
class BlogImageSeqFilter(models.Manager):
def get_querySet(self):
## slice the first three images with sequence between 1 and 3
return self.filter(sequence__gte=1, sequence__lte=3)[:3]
class BlogImage(models.Model):
blog = models.ForeignKey(BlogPost, null=True, on_delete=models.SET_NULL)
img = models.ImageField(upload_to=imgFolder, blank=True)
sequence = models.IntegerField(null=False, blank=False, default=0)
objects = BlogImageSeqFilter() ## Custom Manager class BlogImageSeqFilter
(The manager method comes in a little later in my question - I don't even know if using it has been the right approach so far...)
In my view, the set of blog posts is included contextually in the rendering of the html tmeplate:
def blogPage(request, proj):
## Limit to the newest 5 posts
blogs = project.blogpost_set.all().order_by("-dateStamp")[:5]
return render(request, 'blog/blogPage.html', {"blogs":blogs})
So in my template, I am displaying each blog post in a separate container, and including the set of images relevant to that blog using blogimage_set. A simplification of what I've got so far would be:
{% for post in blogs %}
<div>
{{post.title}}
{{post.post}}
{% for image in post.blogimage_set.get_querySet %}
<img src="yada yada {{image.img}} yada yada></img>
{% endfor %}
</div>
{% endfor %}
Now, what I am stuck on. I want to change the model and the view, so that I can pass an argument somehow to limit the number of items in each post (as I understand it you can't really pass arguments from the template backwards, unless you start faffing with custom template tags which I don't want to do in this case because I'm sure there is a better way of doing it, and I want to learn that way.)
I want something flexible enough that I could re-use the blog images somewhere else, using a different parameter to represent the number of images returned. But it's not as simple as "Give me the first x images in the table".
Say I upload 10 images, I will assign them all sequence #s from 1-10 (hence the 'sequence' field in the model). But that sequence might not be in the same order that I uploaded. So overall I want to be able to choose in a given view which x images from the set of 10 will appear on that blog post. "Give me images between seq #1 and #3" or "Give me 5 images starting from seq #4" or something.
My first incremental attempt towards achieving this functionality was to create the BlogImageSeqFilter Manager and its method which gives a limited queryset of three items having sequence between 1 and 3.
Changing the model (or rather the Manager/its method) to accept an argument seems like the simple bit. But what I can't figure out is how to modify the view and the template to use parameters, in such a way that I can include "blogImages" in the render's context while still displaying them in the template as shown above ("for each post in the given blog ...do some html and... for each x images for that post...do some more html").
I appreciate that changing up the images displayed in a conventional "blog post" after the initial posting isn't a context that makes much sense - but as mentioned I want that flexibility to maybe use elsewhere, or reuse the code for something else at a later date. And I have a feeling I'm stuck because of a knowledge/skill gap or a wider problem with the way I'm currently working, so might as well identify it here.
I think you're overcomplicating things a bit. If you want to control how many images you display in a particular context, you can just slice the image queryset, at the point of use.
So in your template you would do something like this to render the first three images:
{% for image in post.blogimage_set.all|slice:":3" %}
<img src="yada yada {{image.img}} yada yada></img>
{% endfor %}
Note that querysets are lazy, so this will only fetch the first three images from your database.
If somewhere else you want to use more/fewer images, you just slice the queryset differently. I don't think you need a custom model manager to achieve this.

Django -- One Template used with multiple variables

I'm trying to display a list of tickets in an HTML table. The table has various headings to display various aspects of the tickets. I would like to present this same table in a bunch of different locations across my project.
I've made a single template of /templates/shared/ticket_list.html in which presents the ticket list and then I {% include %} it where I need to display this listing. Simple enough.
However, there are a couple of pages in my project where I have a Bootstrap tabbed div. I want to display this table of tickets in both tabs, but with a different set of tickets. The HTML for this essentially requires that I {% include %} my table template twice on the same HTML page.
For example:
Tab 1: "Created By User" -- a list of tickets that the current user created
Tab 2: "Assigned To User" -- a list of tickets that are assigned to the current user
In the view, I might have something like:
created_by_ticks = Ticket.objects.filter(created_by = self.request.user)
assigned_to_ticks = Ticket.objects.filter(assigned_to = self.request.user)
The problem is, in my ticket table template, how would I present both querysets since the table itself is likely expecting a single variable name, such as:
{% for t in tickets %}
<tr>...
{% endfor %}
But passing in two querysets of tickets, I now have two ticket variables of created_by_ticks and assigned_to_ticks.
Any ideas how I could use that single ticket table template, but use multiple variables, or some other solution?
You can use the with functionality of the include tag:
{% include 'shared/ticket_list.html' with tickets=created_by_ticks %}
{% include 'shared/ticket_list.html' with tickets=assigned_to_ticks %}

Relate users to items in a ManyToMany relation, what is best practice?

In my Django project I have a model called Item, which basically stores a lot of items. I also make use of Django's built in User model to allow user's to register and login. What I now want is for each user to be able to check an item as "collected".
I'm not feeling comfortable in the way I've solved it right now, that's why I'm asking if this is good practice for this problem? Or is there any best practice to solve my problem?
This is my solution now:
from django.contrib.auth.models import User
class Item(models.Model):
...
users_collected = models.ManyToManyField(User, related_name='items_collected')
And I use this as: If there is a record between a user and an item, then the item is considered as collected by that user. To mark an item as collected I simply add a record, and to again mark it as not collected, I remove that record.
In my detail template for a specific item category I have the following code to display if a user has an item collected, or not.
{% for item in items %}
{{ item.name }} -
{% if user in item.users_collected.all %}
in collection.
{% else %}
not in collection.
{% endif %}
My main concern using this method to solve the problem is the number of created and removed records for the ManyToMany-table that is created, as users will check and uncheck items regularly. I don't really like to put a new field to the Item's model either, as I guess it makes it less re-usable?
Yes, I think this represents good practice. You need to persistently store the relationship between Users and Items, and your method does so in the most straightforward way possible.
A high database write rate can certainly be problematic, but how else would you store this information? One can imagine more exotic implementations, where, for example, each User has a bit-array field with one bit per Item indicating whether it is collected or not. But unless you have a good reason for doing something like that I would start with this straightforward approach.
One bit of advice I have is to make the M2M through table (see the documentation) explicit. That will promote clarity, and make it easy to add any extra fields as the relationship becomes more complex.

Modifying QuerySet result

Is it possible to change some specific items in a QuerySet object? In my case i'm trying to slicing "title" fields with length more than 40 characters and append "..." at the end of field.
There are 2 ways of doing what you want.
The first is to use a Django filter. So if you are looping through the items of your queryset and displaying them on a page use something like truncatewords. You would use this like this in your template:
{% for item in queryset %}
<h1>{{ item.title|truncatewords:3 }}</h1>
{% endfor %}
It doesn't look like there is a Django filter for truncating base on the number of characters. If you want to write your own filter it's not that hard to do.
The other option is to put a method on your model to do what you want. Here is an example:
#property
def short_title(self):
return '%s...' % self.title[:40]
You would then be able to reference this anywhere in your template as {{ object.short_title }}.
I suggest adding a new property 'adjusted_title' to each object
for item in your_query_set:
if(len(item.title) > 40):
item.adjusted_title = item.title[0:40] + "..."