Context variable to be used in base.html (Django) - python

I need a certain context variable in my base.html. This is to contain a set of usernames, e.g. [name1, name2, name3, name4,]. If a logged in user's username is part of this list, I give said user certain preferential treatment and show something in the navbar.
To achieve this, I wrote a template tag:
from django import template
from django.db import models
from django.contrib.auth.models import User
register = template.Library()
VIPS = [name1, name2, name3, name4,]
#register.simple_tag
def verified(user):
return VIPS
register.simple_tag(verified)
And then in base.html, I added {% load verified %} at the top, and then:
{% if user.username in verified %}
<!-- do something -->
{% endif %}
This isn't working. What am I doing wrong? I suspect I've written my template tag incorrectly, but I've tried several, more complex approaches (in vain), at least this simpler one made logical sense to me.
My project's a legacy Django 1.5 project with Python 2.7.

You don't need the register.simple_tag(verified) line, as the #register decorator is already doing that.
However, you might consider a different approach to avoid additional processing in the template, assuming your user is coming from request.user...
#regsiter.assignment_tag(takes_context=True)
def check_user_is_verified(context):
user = context['request'].user
return user and user in vips
Then in your template:
{% check_user_is_verified as is_verified %}
{% if is_verified %}
{# whatever #}
{% endif %}
By leveraging an assignment tag, you can check if the user is verified once, and leverage the context variable you assign instead of having to perform the same list processing each time.
Another alternative is to use a cached property on a custom User object, or a "Profile" model that is linked to your User model via a OneToOneField.
from django.utils.functional import cached_property
class Profile(models.Model):
user = models.OneToOneField(User)
#cached_property
def is_verified(self):
# get the list of vips here
return self.user in vips
If your list of vips changes, just clear the cache key, which you could do via a signal or a Celery task, etc:
del profile_instance.is_verified
Now you have a very efficient property you can check anywhere in your code. My preference tends to be fat models, skinny views and dumb templates.

Related

How to change the user display name to their first name in Django

I am using the Django inbuilt user authentication system, from from django.contrib.auth.models import User. And I have realised that in the admin page it always displays the username of the user. Is it possible to change the def __str__ (self): method of that function to display a customized one, something like this.
def __str__ (self):
return f"{self.first_name}"
For any such changes, you should refer to the template that's rendering it. Here, it is the Django Admin's base.html template.
As you can see in this line in the file, it searches for both short_name and user_name in that order and displays the first one available.
{% block welcome-msg %}
{% translate 'Welcome,' %}
<strong>
{% firstof user.get_short_name user.get_username %}
</strong>.
{% endblock %}
And get_short_name returns the first name of the user. So, your user does not have their first name defined and hence it's showing up their username.
NOTE : Please check your Django version's documentation since this has been implemented after version 1.5 and above, and is valid only for greater versions.
I highly recommend to extend the auth user model and thus you can do a lot of customizations sooner or later.
You can use lambda function to alter the __str__() method of auth user as
from django.contrib.auth.models import User
User.__str__ = lambda user_instance: user_instance.first_name
Note: This snippet should get executed on the Django server initialization
If you are trying to change the username which is shown at the top-right-corner (as mentioned by #amit sigh ), set the lambda function for get_short_name as,
from django.contrib.auth.models import User
User.get_short_name = lambda user_instance: f"Prefix : {user_instance.first_name} : Suffix"

How to prevent users from seeing data that does not belong to them in Django DetailView?

I have a web app where a user signs in and begins entering items for a ToDoList. The base.html is wrapped in an is_authenticated check so users cannot see anything in the app until they have logged in. I was doing some testing with:
Logging in as User2
Adding a new ToDoListItem
Redirecting to the DetailView
In this case, the URL = http://localhost:8000/to_do_list/to_do_item/72
At this point I realized that the DetailView would allow User2 to see the details for any ToDoListItem for User1 just by entering in an existing pk into: http://localhost:8000/to_do_list/to_do_item/<int:pk>.
urls.py includes
path('to_do_item/<int:pk>', views.ToDoListItemDetail.as_view(), name='todo-item-detail'),
views.py
class ToDoListItemDetail(DetailView):
model = ToDoListItem
todolistitem_detail.html
{% extends 'base.html' %}
{% block content %}
Home
<h1>DetailView for 'ToDoListItem' model</h1>
<p>TaskTitle: '{{ object.title }}'</p>
<p>Complete: '{{ object.is_complete }}'</p>
<p>User: '{{ object.user}}'</p>
{% endblock %}
What is the recommended way to prevent this from happening? I am considering the following:
I could completely remove the DetailView and direct to a different URL that only returns the user's data (using something like ToDoListItem.objects.filter(user=request.user))
I could check that the name of the user logged in matches the name of the user that owns the ToDoListItem.
I could override get_context_data() for the DetailView and check user ownership there (similar to no. 1, but within the DetailView)
??? (Something else better than the above I do not know about yet)
Is there a way to limit a user to only see their own data throughout an application without implementing this logic every time it is needed?
You can filter in the DetailView as well, by overriding the get_queryset method [Django-doc]:
from django.contrib.auth.mixins import LoginRequiredMixin
class ToDoListItemDetail(LoginRequiredMixin, DetailView):
model = ToDoListItem
def get_queryset(self, *args, **kwargs):
return super(ToDoListItemDetail, self).get_queryset(
*args, **kwargs
).filter(user=self.request.user)
Django will, behind the curtains, always call get_queryset(..). By default this function returns the queryset of the model you specified with all objects. But you thus can filter it further down.
Django's get_object method [Django-doc] will then further filter it down with the id and/or slug, but if you already filter out the elements that do not belong to the self.request.user, then this can thus only result in an query returning no results.
It also makes sense to here add the LoginRequiredMixin [Django-doc] to your class, since in case the user did not log in, you probably want to redirect hem/her to the login screen.
There is a permission named: ¨can read¨ that allow you to handle the access. For example:
class FruitEdit(PermissionRequiredMixin,DetailView):
permission_required = 'app.read_fruit'
...
I hope you can solve it

I want to use the if statement based on the existence of a web page

So basically i have a complicated scenario. I am current using Django to build a website and i have current made two apps. Both apps have almost identical fields. The field I would want to focus on though is the Information field(which they both have and which i have auto generated with the help of the Wikipedia model)
So the case here is that I want to create an if and else statement in the html in such a way that if the page i am hyperlinking to exists it would go to the link dealing with DetailView but if it doesnt exist I would redirected to the Create View
I should also note that the two apps have their names linked with the help of the foreign key but when i try to open information links using the same name , they gave me different pks
I dont feel like i explained my problem well enough but I hope someone can understand what i mean
UPDATE
ok I create the get function using
def get(self, request, *args, **kwargs):
try:
self.object = self.get_object()
except Http404:
return redirect('/create/')
context = self.get_context_data(object=self.object)
return self.render_to_response(context)
but i dont know how to use the CreateView fuction i created instead of the link i put
This is the Detail View Html
{%extends "home.html"%}
{%block head_title%} {{block.super}}{%endblock head_title%}
{% block content%}
<!-- verify authentication -->
{% if request.user.is_authenticated%}
<h3>{{object.title}}</h3><br/>
{% endif %}
<ul type="disc">
<div class="container">
<li><b>Artist: </b>{{object.Summary}}</li>
<li><b>Genre: </b>{{object.Genre}}</li>
<li><b>Bio: </b><br>{{object.Bio}}</li>
EDIT
</div>
</ul>
{%endif%}
{% endblock %}
This is my model
from django.db import models
from django.conf import settings
from Blog.models import MainPage
from django.urls.base import reverse
from Blog.Retrieve import retriever
from django.db.models.signals import pre_save,post_save
import InfoPedia
class InfoPedia(models.Model):
user =models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE)
Name =models.ForeignKey(MainPage,on_delete=models.CASCADE)
Location =models.CharField(max_length= 50,null=True,blank=True)
Information =models.TextField(null=True,blank=True)
TrackListing=models.TextField(null=True,blank=True)
Published=models.BooleanField(default=True)
Timestamp=models.DateTimeField(auto_now=True)
Updated=models.DateTimeField(auto_now=True)
def get_absolute_url(self):
# return f"/Blog/{self.slug}"
return reverse('InfoPedia:DetailView', kwargs={"pk":self.pk})
class Meta:
ordering=["-Updated","-Timestamp"] #orranges in order of updated
def get_tracklist(self):
return self.TrackListing.split(",")
def Information_create_pre_save( instance, sender, **kwargs):
instance.Information=retriever(instance.Name)
def rl_post_save_reciever(sender, instance,created,*args,**kwargs):
print("saved")
print(instance.Timestamp)
pre_save.connect(Information_create_pre_save, sender=InfoPedia)
post_save.connect(rl_post_save_reciever, sender=InfoPedia)
An alternative - rather than checking the if/else in the HTML, just make all the links to the DetailView URL.
Then, in the get() handler for the DetailView, you perform a queryset lookup for the object. If no object is found, then instead of displaying the DetailView HTML, return to the user a 302 redirect (i.e. a temporary redirect) to the CreateView for that object. So all your if/else logic is in the view function or class, instead of HTML.

how to extend django views/templates in a forward looking way

i'm looking for a way to extend a django template/view.
my first implementation consists of two models (clients/models.py):
class Client(models.Model):
...
class Address(models.Model):
client = models.ForeignKey(Client)
...
and its fairly simple template (clients/detail.html) :
{{client.name}}
Address: {{client.address.street}}, {{client.address.zipcode}} {{client.address.city}}
as my application grows, a new app was born: 'invoices'.
it is again very simple (invoices/models.py):
class Invoice(models.Model):
client = models.ForeignKey(clients.models.Client)
...
now my clients details-view needs to display invoices, so i create and override clients/detail.html in my 'invoices' app.
good for now.
later on i created a third app 'quotes'.
again my clients details-view needs to display quotes.
if i create clients/detail.html in my 'clients' i will loose the ability to display invoices.
because the 'invoices' and 'quotes' app are indipendent.
my first idea was to create something like a SubView-class
which 'invoices' and 'quotes' can extend and then register their implementation somewhere.
a template should look like this:
{{client.name}}
Address: {{client.address.street}}, {{client.address.zipcode}} {{client.address.city}}
{% for view in views %}
<h1>{{view.title}}</h1>
{{view.get_html}}
{% endfor %}
is this a good way to go and should i use a admin.site-like implementation for registering my sub-views?
In Django one url in urls.py should ideally use one view, just to keep things simple.
I would therefore adopt the approach of putting all the required context in your one view for this screen (I think you already have this via foreign keys in your model). Then, rather than doing what you call "SubView-class" I would go for the Django template include tag.
Example:
{% for invoice in client.invoices %}
{% include "invoice-detail.html" with invoice=invoice %}
{% endfor %}
This renders each invoice's detail for all the invoices of the client. Notice how this is in line with the DRY principle.

How to display total record count against models in django admin

Is there a neat way to make the record/object count for a model appear on the main model list in the django admin module?
I have found techniques for showing counts of related objects within sets in the list_display page (and I can see the total in the pagination section at the bottom of the same), but haven't come across a neat way to show the record count at the model list level.
I would look into the models.Manager class. A subclass of Manager will allow you to add table-level functionality to your models. A Manager method can return any data you want and there is an interesting example in the Django DB API documentation. You may then be able to pull this into Admin by adding a admin inner class to your model.
from django import template
from django.db.models.loading import get_model
register = template.Library()
#register.simple_tag()
def get_model_count(admin_url):
app_label, model_name = admin_url.split('/')[:2]
return get_model(app_label, model_name, seed_cache=False).objects.count()
Then copy and override "/templates/admin/index.html" from "django's contrib/admin/templates/index.html".
At the top add:
{% load NAME_OF_YOUR_TAG_FILE %}
Add the following call after the model name or wherever:
{% get_model_count model.admin_url %}
This fits nicely into this use case. You're done!
I didn't find any nice way to add count of models in the main admin page, but here is the solution that I finally use.
In short I compute the counts of each models in signals post_delete and post_save methods, store the variables in the custom request (in a map) and display it in the extended admin index.html by simply checking with an if for each desired models.
The extended templates/admin/index.html:
{% if model.perms.change %}
<th scope="row"><a href="{{ model.admin_url }}">{{ model.name }}
{% if model.name == "Mymodel1_verbose_name_plural" %} ({{ MODELS_COUNT.Mymodel1}}) {% endif %}
</a></th>
{% else %}
My custom request in util/context_processors.py:
from myproject import settings
def myproject(request):
return {
'request' : request,
'MODELS_COUNT' : settings.MODELS_COUNT
}
In my settings.py:
MODELS_COUNT = {
'Mymodel1': None,
'Mymodel2': None
}
TEMPLATE_CONTEXT_PROCESSORS = (
...
'myproject.util.context_processors.myproject',
)
In myproject.__init__.py:
from django.db.models.signals import post_save, post_delete
def save_mymodel1_count(sender, instance=None, **kwargs):
if kwargs['created']:
settings.MODELS_COUNT['Mymodel1'] = Mymodel1.objects.count()
def delete_mymodel1_count(sender, instance=None, **kwargs):
settings.MODELS_COUNT['Mymodel1'] = Mymodel1.objects.count()
settings.MODELS_COUNT['Mymodel1'] = Mymodel1.objects.count()
post_save.connect(save_mymodel1_count, sender=Mymodel1)
post_delete.connect(delete_mymodel1_count, sender=Mymodel1)
If you have lots of models, I suggest that you transform this in a more generic solution.

Categories

Resources