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

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 :)

Related

I dont know how to pass two queryset parameters into one URL

I really struggled to explain my problem and the only way I found it would be possible is - through screenshots as I have a lot of code and I am not sure what is really needed here. So if you want any code, tell me I will add.
The numbers on the pictures indicate the order.
Choosing the category
Selecting the category it redirects me to - /products_list?category=(that category_id)
Filtering through brand in that category
Now please pay attention to the URL and what happens after I have chosen the brand I want to filter with.
Back on the first page
Problem is here:
Now I am back on the first page, where are all the products but I wanted it to stay on that URL where are that kind of category products.
What I wanted to happen? Instead of it taking me to the page where are ALL the products and then doing the filtering, I want it to stay on that category page and return the filtered products there.
The brand dropdown menu also should only show that category products that I am in, not all.
You need to pass the other parameters as well. So that means that for two parameters category_id and brand, you create a URL that looks like:
{% url 'product-list' %}?category={{ category_id|urlencode }}&brand={{ brand|urlencode }}
If you thus already filtered the category down, you pass the category_id to the template, and render the URLs with the ?category={{ category_id|urlencode }} part.

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.

How can I add extra data to a ModelMultipleChoiceField?

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.

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?

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.