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

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.

Related

Is it better to have an explicit django model field or calculate on demand?

If I have a django model that has an field called 'order' (integer field that tracks the modifiable order of a specific model instance amongst all model instances), and I also want to track the position of the model instance based on that order ('first'/'mid'/'last'), is it better to have another model field called 'position', which I recalculate and store each time there is a change to order, or should I have a model method called 'get_position' which I call each time I need a position. I'm trying to work out which is more efficient. Whilst I won't need the position information each time I create/delete/modify a instance order, I will need that position field quite a bit within my templates.
Generally avoid creating an additional database fields if something can be calculated from data already in the db, to prevent redundancy and reduce vectors for errors.
A method may work if its relatively simple, deriving its value from an existing field within the record. If it needs to look up comparative values from other records that would be an efficiency issue.
As you mention use in templates specifically, it may be worth seeing if the template builtins can meet your requirements.
Say your recordset is already in order of the 'order' field eg,
MyRecords.objects.order_by('order')
When looping through MyRecords_context in a template using a for loop, you can test for {{if forloop.first}} or {{if forloop.last }} - you can also refer to MyRecords_context.first() and .last() to single those out. If you need the specific index of a given record in that particular recordset for you template, you can refer to {{ forloop.counter }} eg {{if forloop.counter < 10 and forloop.counter > 5}} or {{if forloop.counter == 5}}
The advantage of this approach is that if you have a filtered a subset of your records, you are still referring to the first, last or Nth records of the recordset, even though they may not be first and last overall in your absolute position field, and with no additional db calls.

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 form with multiple choice field with potentially infinite choices

I have a model with a many-to-many field that will expand as the user adds more entries for the model. In the form template if I use the conventional {{ form.field }} I get a multiple choice select as is expected, but the problem that will quickly become apparent is when there are 10, 100, 1000 or more choices. What I'd like to provide in the form is a search field where the user can search through all available entries and via AJAX return entries matching their search criteria where they can then select the individual entry choices to be saved in the database.
I'm assuming I will have to manually render the multiple choice form field, but after hours of research I cannot find an example of this anywhere online. Is there an example that exists and I just haven't been able to find? How is one supposed to manually create a form with a multiple choice field in Django? Or am I going about this all wrong?

Django: How to select a limited amount of rows for each foreign key?

class Comment (models.Model):
user = models.ForeignKey(User, related_name="comments")
title = models.TextField(max_length=256)
comment = models.TextField(max_length=1286)
created = models.DateTimeField(db_index=True)
Now how do I get latest comments but limit the results to only (say 3) comments per same user?
Here is how its done in pure SQL: How to select a limited amount of rows for each foreign key?
I guess, this would work:
Comment.objects.filter(*[
~Q(id__in=user.comments.order_by('-created')[3:].values_list('id', flat=True))
for user in User.objects.all()
])
However, it seems like neither pythonic nor efficient way to solve the problem. (Efficiency problem could be somewhat solved by using cache, but still.)
What are you trying to achieve, anyway? You could just fetch all user's comments and use only the latest three. Assuming that your comments are ordered by -created field:
{% for user in users %}
{% for comment in user.comments.all|slice:"3" %}{{ comment }}{% endfor %}
{% endfor %}
Slicing will be converted to LIMIT clause in SQL query, so you won't be getting all the comments anyway. Of course, there would be more that one query executed to get all the comments, so using template caching can help.
Again, this may not make sense in your case. If it doesn't, it would be better if you clarify the requirements.
Raw SQL
If you know how to do what you want with raw SQL ([1] may help), then you need to just find a way to put it into extra() somehow ([2] is an example of complex extra query which may give you an idea).
[1] How to select a limited amount of rows for each foreign key?
[2] django's .extra(where= clauses are clobbered by table-renaming .filter(foo__in=... subselects