Django Foreign Key to Permission - django

class Application(models.Model):
name = models.CharField(max_length=100)
icon_url = models.CharField(max_length=100)
app_url = models.CharField(max_length=200)
I would like to add another field for required_permission which should be a foreign key to a permission. What should that field definition look like?
The reasoning behind this is that in a template, I can use something like this (psuedo code)
{% for app in application_list %}
{% if user_has_permission(app.required_permission) %}
Show App
{% endif %}
{% endfor %}

Your required_permission can be a CharField. As long as you can create a list of permissions to use with this function.
has_perms(perm_list, obj=None)
Returns True if the user has each of the specified permissions, where
each perm is in the format "<app label>.<permission codename>". If the
user is inactive, this method will always return False.
If obj is passed in, this method won’t check for permissions for the
model, but for the specific object.
Note that you can't call function that takes parameter in Django templates, You should write a template tag.

Related

django How get creator username instead id using two for

I want get creator username instead id.
The item.r.username dont work.
item.r.bornplace work correctly.
Where i do mistake?
My model.py:
class Rec(models.Model):
creator = models.ForeignKey(auth.get_user_model(), on_delete=models.CASCADE)
bornplace = models.CharField(default='default')
My views.py
def lists(request):
list = Rec.objects.all()
lists = []
for r in list:
lists.append({'r':r})
context = {'lists': lists}
return render(request, 'lists.html', context)
My lists.html
{% for item in lists %}
{{ item.r.username }}
{{ item.r.author_id }}
{{ item.r.bornplace }}
{% endfor %}
For your issue you should use the Foreign Key mapping in the model fields you have defined from creator as defined below:
class Rec(models.Model):
...
creator = models.ForeignKey(auth.get_user_model(), on_delete=models.CASCADE)
In this instance I can see you are using Django's auth, and without going into too much detail their default model stores username, so from that your username attr can be accessed from your creator instance. With that, overall your template call should have been:
{{ item.r.creator.username }}
But this idea is not specific to Django's auth system, for all FK relationships defined in your models you can use the standard syntax to access fields as needed, both in templates and in views.
Models have attributes so use nested access to gather the values you need. For example:
model.FKField.field or model.FKField.method()
Naturally in templates there are no parentheses, so for using a method in a template the syntax is:
{{ model.FKField.method }}
you should do it in this way:
{{ item.r.creator.username }}

How do I display Django ManyToMany on a template? Simple code needed

I am trying to display ManyToMany field on the template in reversed order.
Here is what I mean:
I managed to display ManyToMany field on template when ManyToMany field was a field in model used so for example:
<br/>{% for tag in post.tag.all %}{{ tag }}<br/>{% endfor %}
will display all of the tags(meaning categories) that the post belongs to based on this model:
class Post(models.Model):
tag = models.ManyToManyField(Tag,blank=True,null=True,related_name='tag')
Now I want something opposite - display authors of the post when ManyToMany field is in the Author model (Post model above stays the same):
class Person(models.Model):
post=models.ManyToManyField(Post,blank=True,null=True,related_name='post')
I am quite sure it has something to do with Related Object Reference ( https://docs.djangoproject.com/en/2.2/ref/models/relations/)
Just can not make it work.
I have tried the following on the template.
{% for post in posts %}
{% for author in post.person_set.all %}{{author}}<br/>{% endfor %}
{% endfor %}
Also, shall I do this kind of searches on the template like above or is it a better practice put this kind of searches in views...resourcewise.
Thanks for help.
You have a misunderstanding on what the related_name= parameter [Django-doc] does. Like the documentation says:
The name to use for the relation from the related object back to this one. (...)
So it is the name of the relation in reverse. In order to make your models "sound", you thus should name it like:
class Person(models.Model):
posts = models.ManyToManyField(Post, blank=True, null=True, related_name='authors')
It also makes sense here to use the plural, so posts instead of post.
In that case, you thus can render this with:
{% for post in posts %}
{% for author in post.authors.all %}{{author}}<br/>{% endfor %}
{% endfor %}
Note that if you want to render all the values for ManyToManyFields, you better use .prefetch_related(..) in the queryset to prefetch the Person,s otherwise rendering the template will result in a lot of extra queries.

Django filter related_name subset in templates

I have a foreign key and I'm using the related_name field like so:
class Pizza(models.Model):
...
restaurant = models.ForeignKey('Restaurant', related_name='pizzas_offered')
active = models.BooleanField(...)
...
Example from the view:
my_restaurant = get_object_or_404(Restaurant, pk=id)
In any template I can run something like my_restaurant.pizzas_offered.all to get all the pizzas belonging to a particular restaurant. However, I only want the pizzas that are active (active=True). Is there a way to retrieve this subset in the template, without having to pass a separate variable in the view? Please note that I always want to only show the active pizzas only so if I have to make a change in the model to make this happen, that is fine too.
NOTE: in the view I can simply pass my_restaurant.pizzas_offered.filter(active=True) but it returns an error when I use it in the template:
{% for details in my_restaurant.pizzas_offered.filter(active=True) %}
{{ details.name }}
{% endfor %}
It returns this error:
Could not parse the remainder: '(active=True)'
There are some reasons why I want to do this on template level and not in the view (main reason: I often loop through all the records in the database and not just one restaurant, so I can't just query for the one record). So my question is how to do this on template-level.
You need to create a Manager for your Pizza model and set the Meta.base_manager_name:
class PizzaManager(models.Manager):
def active(self):
return self.filter(status=True)
class Pizza(models.Model):
...
objects = PizzaManager()
class meta:
base_manager_name = 'objects'
Now you can use the method active in your template:
{% for details in my_restaurant.pizzas_offered.active %}
...
{% endfor %}
For more information you can read the documentation about Default and Base managers.

django template arguments and outer joins

Sorry about the strange title, but I couldn't explain the situation in a few words. Let me articulate:
I have a Jobs model whose objects I show in a template. Against each job, I also want to show if the user has already applied for that job.
I have these models
class Job(models.Model):
is_valid = models.BooleanField()
description = models.CharField()
def has_user_applied(self, user):
return jobapplication_set.filter(applicant=user).exists()
class JobApplication(models.Model):
applicant = models.ForeignKey(User)
job = models.ForeignKey(Job)
cover_letter = models.CharField()
And a view in which I fetch all the Jobs:
jobs = Job.objects.filter(is_valid=True)
return HttpResponse( ... {'jobs': jobs} ... )
And a template in which I list them:
{% for j in jobs %}
{{ j.description }} {% if j.has_applied %} (You've already applied) {% endif %}
{% endfor %}
However the "has_applied" function takes "user" as argument and passing arguments in templates is not allowed.
Now I have two issues:
Can I create a "context" so that some functions can assume that a particular user is in question rather than it being passed explicitly and limiting its usage in templates? If it is not possible, what is the elegant way of annotating this information in the model objects in the views?
Secondly, even If I'm able to do this, for each Job object, I still have to execute a separate query to determine if user has already applied. I know this is possible in raw SQL using outer joins, but can I do it using django's ORM?
Easiest and most verbose way of doing it will be just:
view
jobs_applied_by_user = set(JobApplication.objects
.filter(applicant=user)
.distinct()
.values_list('job', flat=True))
template
{% for job in jobs %}
{% if job.pk in jobs_applied_by_user %}
…
One simple solution is to write the custom filter.
{% for j in jobs %}
{{ j.description }} {% if j|has_applied:user %} (You've already applied) {% endif %}
{% endfor %}
Here has_applied is custom filter and it will take user as parameter.

django get list of distinct 'children' of ForeignKey related model (and do this in template?)

I'm making a database of released music albums
models.py
class Image(models.Model):
image = models.ImageField(....
class Album(models.Model):
title = models.CharField(....
class Release(models.Model):
album = models.ForeignKey(Album)
cover_art = models.ForeignKey(Image, blank=True, null=True, on_delete=models.SET_NULL)
In my template (at the moment I'm using generic views) I have:
{% for a in album_list %}
{% for r in a.release_set.all %}
{% if r.cover_art %}
# display cover art image
{% endif %}
{% endfor %}
{% endfor %}
The problem is that sometimes an album has been released several times with identical cover art, in which case I'd like to display the image only once, with some text listing the releases it pertains to.
I've tried:
{% for i in a.release_set.cover_art %}
{% for i in a.release_set.cover_art_set %}
{% for i in a.release_set.all.cover_art %}
{% for i in a.release_set.all.cover_art_set %}
Or in a simpler case, I'd at least like to display the images smaller if there are more than one of them.
{% if a.release_set.count > 1 %} # works but displays duplicate images
{% if a.release_set.cover_art_set.count > 1 %} # doesn't work (see above)
Is it possible to get a list of objects related by reversing this ForeignKey lookup then asking for the set of their children? The only way I can think of is by assembling some tuples/lists in the view.
I managed this with a new method on the Album model:
class Album(models.Model):
title = models.CharField(....
def distinct_cover_images(self):
"Returns the queryset of distinct images used for this album cover"
pks = self.release_set.all().values_list('cover_art__pk', flat=True)
distinct_cover_images = Images.objects.filter(pk__in=pks).distinct()
return distinct_cover_images
Then the template is much more simple:
{% for i in a.distinct_cover_images %}
Credit to #danilobargen however for his contribution to this code.
If I understood this right:
An album can have several releases
A release has only one cover
You want to loop over all covers of an album
In that case, the following should work:
{% for release in a.release_set.all %}
{{ release.cover_art.image }}
{% endfor %}
If you want to prevent listing identical covers, you can either compare the covers in the loop, or prepare a set with distinct covers in your view, so you can pass it on to the template.
# Solution using a set
context['distinct_coverimages'] = \
set([r.cover_art.image for r in album.release_set.all()])
# Solution using two queries, might perform better
pks = album.release_set.values_list('cover_art__pk', flat=True)
context['distinct_coverimages'] = models.Image.filter(pk__in=pks).distinct()
A third alternative would be creating a custom template filter for your album, to return all distinct release covers.
In any case, I recommend debugging such things in your Django shell. You can issue the shell with ./manage.py shell. If you have installed django-extensions, you can also use ./manage.py shell_plus to autoload all models. All object attributes and functions that don't require arguments (e.g. normal instance attributes or instance functions without arguments like 'string'.isalnum()) can also be used the same way (just without the parentheses) in your template.