I would like to know how to show an error message in the Django admin.
I have a private user section on my site where the user can create requests using "points". A request takes 1 or 2 points from the user's account (depending on the two type of request), so if the account has 0 points the user cant make any requests... in the private user section all this is fine, but the user can also call the company and make a request by phone, and in this case I need the admin to show a custom error message in the case of the user points being 0.
Any help will be nice :)
Thanks guys
One way to do that is by overriding the ModelForm for the admin page. That allows you to write custom validation methods and return errors of your choosing very cleanly. Like this in admin.py:
from django.contrib import admin
from models import *
from django import forms
class MyForm(forms.ModelForm):
class Meta:
model = MyModel
def clean_points(self):
points = self.cleaned_data['points']
if points.isdigit() and points < 1:
raise forms.ValidationError("You have no points!")
return points
class MyModelAdmin(admin.ModelAdmin):
form = MyForm
admin.site.register(MyModel, MyModelAdmin)
Hope that helps!
I've used the built-in Message system for this sort of thing. This is the feature that prints the yellow bars at the top of the screen when you've added/changed an object. You can easily use it yourself:
request.user.message_set.create(message='Message text here')
See the documentation.
Django versions < 1.2 https://docs.djangoproject.com/en/1.4/ref/contrib/messages/
from django.contrib import messages
messages.add_message(request, messages.INFO, 'Hello world.')
Related
Have created a form but unsure if is right and also unable to add a user, it will show TypeError/
This is how the form I want it to look like
The following is my coding:
class Form_Add_User(forms.Form):
name=forms.CharField(label="Name", max_length=50)
dateofbirth=forms.DateField(label="Date of Birth", widget=forms.widgets.DateInput(format="%m/%d/%Y"))
contactnum=forms.CharField(label="Contact Number", max_length=9)
def adduser(request):
if len(request.POST)>0:
form=Form_Add_User(request.POST)
if(form.is_valid()):
name=form.cleaned_data['name']
dateofbirth=form.cleaned_data['dateofbirth']
contactnum=form.cleaned_data['contactnum']
new_user=User(name=name,dateofbirth=dateofbirth,contactnum=contactnum)
new_user.save()
return redirect('/adduser')
else:
return render(request,'adduser.html',{'form':form})
else:
form=Form_Add_User
return render(request,'adduser.html',{'form':form})
First off: it's always very useful to also post a full error message if you have one. The more info you give us, the easier (and quicker!) is answering your question.
I assume, your User model is not actually django's auth User model (see, if you had posted the model, I wouldn't have to guess).
The form you pass to your template was not instantiated:
#...
else:
form=Form_Add_User() #!!
return render(request,'adduser.html',{'form':form})
I don't know this is a typical thing to do in web application, but what we achieve is that, let's say we have a Person model, inside this model, we have a FileField stores user's photo:
class Person(models.Model):
photo = models.FileField(upload_to='Person_photo')
What I want to achieve is that only the owner can see his or her photo. People who log on a different account or even not log in should not be able to see the photo. Let's assume we have a way to check whether a photo belongs to a certain user or not:
def permission(photo_filename, pid):
# return True if photo_filename exists and belongs to the person pid
We can figure this part out, for example, use permission system provided in Django. Of course from the views.py we can control whatever image we want to show on the page, but for example, we want to block people make attempts to figure out the URLs and get the photo, for example, typing
http://some.domain/media/Person_photo/Amy.jpg
in URL bar in the browser should only work if she is Amy. What is a good way to do it? Is there a library for this purpose?
You can define view for this
view.py
from django.http import HttpResponse
from django.shortcuts import get_object_or_404
from django.contrib.auth.decorators import login_required
from appname.models import Person
#login_required
def show_photo(request):
person = get_object_or_404(Person, user=request.user)
response = HttpResponse(person.photo.read(), content_type="image/jpg")
response['Content-Disposition'] = 'filename=photo.jpg'
return response
urls.py
urlpatterns += [
url(r'^photo/$', show_photo),
]
Users will only see their photo.
I couldn't find an answer to the following question, it took me a couple of hours to find out, hence I'm adding it. I'll add my approach of solving it and the answer.
I'm following a YouTube tutorial from This person. For some reason I'm typing the same code, and I checked every single letter. Yet for some reason my cleaning functions aren't called. It's probably something simple, especially since a related question showed something similar. It's probably a framework thing that I get wrong, but I wouldn't know what it is.
Here is the relevant code.
forms.py (complete copy/paste from his Github)
from django import forms
from .models import SignUp
class ContactForm(forms.Form):
full_name = forms.CharField(required=False)
email = forms.EmailField()
message = forms.CharField()
class SignUpForm(forms.ModelForm):
class Meta:
model = SignUp
fields = ['full_name', 'email']
### exclude = ['full_name']
def clean_email(self):
email = self.cleaned_data.get('email')
email_base, provider = email.split("#")
domain, extension = provider.split('.')
# if not domain == 'USC':
# raise forms.ValidationError("Please make sure you use your USC email.")
if not extension == "edu":
raise forms.ValidationError("Please use a valid .EDU email address")
return email
# Final part is ommited, since it's not relevant.
admin.py (typed over from the tutorial)
from django.contrib import admin
# Register your models here.
from .models import SignUp
from .forms import SignUpForm
class SignUpAdmin(admin.ModelAdmin):
list_display = ['__unicode__', 'timestamp', 'updated']
class Meta:
model = SignUp
form = SignUpForm
admin.site.register(SignUp, SignUpAdmin)
After using print statements for a while and reading questions that seemed similar but eventually didn't solve my problem, I decided to look into the source of Django (idea inspired by the most similar question I could find).
Then, I decided to debug the source, since I wanted to know how Django is treating my customized function (thanks to a tutorial + SO answer). In the source I found that the customized functions were called around return super(EmailField, self).clean(value) (line 585, django/forms/fields.py, Django 1.8). When I was stepping through the code I found the critical line if hasattr(self, 'clean_%s' % name): (line 409, django/forms/forms.py, Django 1.8). I checked for the value name which was "email". Yet, the if-statement evaluated as False ((Pdb) p hasattr(self, 'clean_%s' % name)). I didn't understand why, until I figured out that the function name was not registered ((Pdb) pp dir(self)).
I decided to take a look at the whole source code repository and cross-checked every file and then I found that
class Meta:
model = SignUp
form = SignUpForm
means that form / SignUpForm were nested inside the Meta class. At first, I didn't think much of it but slowly I started to realize that it should be outside the Meta class while staying main class (SignUpAdmin).
So form = SignUpForm should have been idented one tab back. For me, as a Django beginner, it still kind of baffles me, because I thought the Meta class was supposed to encapsulate both types of data (models and forms). Apparently it shouldn't, that's what I got wrong.
I'm new to the web development world, to Django, and to applications that require securing the URL from users that change the foo/bar/pk to access other user data.
Is there a way to prevent this? Or is there a built-in way to prevent this from happening in Django?
E.g.:
foo/bar/22 can be changed to foo/bar/14 and exposes past users data.
I have read the answers to several questions about this topic and I have had little luck in an answer that can clearly and coherently explain this and the approach to prevent this. I don't know a ton about this so I don't know how to word this question to investigate it properly. Please explain this to me like I'm 5.
There are a few ways you can achieve this:
If you have the concept of login, just restrict the URL to:
/foo/bar/
and in the code, user=request.user and display data only for the logged in user.
Another way would be:
/foo/bar/{{request.user.id}}/
and in the view:
def myview(request, id):
if id != request.user.id:
HttpResponseForbidden('You cannot view what is not yours') #Or however you want to handle this
You could even write a middleware that would redirect the user to their page /foo/bar/userid - or to the login page if not logged in.
I'd recommend using django-guardian if you'd like to control per-object access. Here's how it would look after configuring the settings and installing it (this is from django-guardian's docs):
>>> from django.contrib.auth.models import User
>>> boss = User.objects.create(username='Big Boss')
>>> joe = User.objects.create(username='joe')
>>> task = Task.objects.create(summary='Some job', content='', reported_by=boss)
>>> joe.has_perm('view_task', task)
False
If you'd prefer not to use an external library, there's also ways to do it in Django's views.
Here's how that might look:
from django.http import HttpResponseForbidden
from .models import Bar
def view_bar(request, pk):
bar = Bar.objects.get(pk=pk)
if not bar.user == request.user:
return HttpResponseForbidden("You can't view this Bar.")
# The rest of the view goes here...
Just check that the object retrieved by the primary key belongs to the requesting user. In the view this would be
if some_object.user == request.user:
...
This requires that the model representing the object has a reference to the User model.
In my project, for several models/tables, a user should only be able to see data that he/she entered, and not data that other users entered. For these models/tables, there is a user column.
In the list view, that is easy enough to implement, just filter the query set passed to the list view for model.user = loggged_id.user.
But for the detail/update/delete views, seeing the PK up there in the URL, it is conceivable that user could edit the PK in the URL and access another user's row/data.
I'm using Django's built in class based views.
The views with PK in the URL already have the LoginRequiredMixin, but that does not stop a user from changing the PK in the URL.
My solution: "Does Logged In User Own This Row Mixin"
(DoesLoggedInUserOwnThisRowMixin) -- override the get_object method and test there.
from django.core.exceptions import PermissionDenied
class DoesLoggedInUserOwnThisRowMixin(object):
def get_object(self):
'''only allow owner (or superuser) to access the table row'''
obj = super(DoesLoggedInUserOwnThisRowMixin, self).get_object()
if self.request.user.is_superuser:
pass
elif obj.iUser != self.request.user:
raise PermissionDenied(
"Permission Denied -- that's not your record!")
return obj
Voila!
Just put the mixin on the view class definition line after LoginRequiredMixin, and with a 403.html template that outputs the message, you are good to go.
In django, the currently logged in user is available in your views as the property user of the request object.
The idea is to filter your models by the logged in user first, and then if there are any results only show those results.
If the user is trying to access an object that doesn't belong to them, don't show the object.
One way to take care of all of that is to use the get_object_or_404 shortcut function, which will raise a 404 error if an object that matches the given parameters is not found.
Using this, we can just pass the primary key and the current logged in user to this method, if it returns an object, that means the primary key belongs to this user, otherwise it will return a 404 as if the page doesn't exist.
Its quite simple to plug it into your view:
from django.shortcuts import get_object_or_404, render
from .models import YourModel
def some_view(request, pk=None):
obj = get_object_or_404(YourModel, pk=pk, user=request.user)
return render(request, 'details.html', {'object': obj})
Now, if the user tries to access a link with a pk that doesn't belong to them, a 404 is raised.
You're going to want to look into user authentication and authorization, which are both supplied by [Django's Auth package] (https://docs.djangoproject.com/en/4.0/topics/auth/) . There's a big difference between the two things, as well.
Authentication is making sure someone is who they say they are. Think, logging in. You get someone to entire their user name and password to prove they are the owner of the account.
Authorization is making sure that someone is able to access what they are trying to access. So, a normal user for instance, won't be able to just switch PK's.
Authorization is well documented in the link I provided above. I'd start there and run through some of the sample code. Hopefully that answers your question. If not, hopefully it provides you with enough information to come back and ask a more specific question.
This is a recurring question and also implies a serious security flaw. My contribution is this:
There are 2 basic aspects to take care of.
The first is the view:
a) Take care to add a decorator to the function-based view (such as #login_required) or a mixin to the class-based function (such as LoginRequiredMixin). I find the official Django documentation quite helpful on this (https://docs.djangoproject.com/en/4.0/topics/auth/default/).
b) When, in your view, you define the data to be retrieved or inserted (GET or POST methods), the data of the user must be filtered by the ID of that user. Something like this:
def get(self, request, *args, **kwargs):
self.object = self.get_object(queryset=User.objects.filter(pk=self.request.user.id))
return super().get(request, *args, **kwargs)
The second aspect is the URL:
In the URL you should also limit the URL to the pk that was defined in the view. Something like this:
path('int:pk/blog-add/', AddBlogView.as_view(), name='blog-add'),
In my experience, this prevents that an user sees the data of another user, simply by changing a number in the URL.
Hope it helps.
In django CBV (class based views) you can prevent this by comparing the
user entered pk and the current logged in user:
Note: I tested it in django 4 and python 3.9.
from django.http import HttpResponseForbidden
class UserDetailView(LoginRequiredMixin, DetailView):
model = your_model
def dispatch(self, request, *args, **kwargs):
if kwargs.get('pk') != self.request.user.pk:
return HttpResponseForbidden(_('You do not have permission to view this page'))
return super().dispatch(request, *args, **kwargs)
I am designing an admin interface where invite mails will be sent to users. My Invitation model is ready & in my invitation admin interface I am able to see my added users for which the admin can send email invites.
now I want to customize this a bit. I want to add for each row a SEND button which will actually send an email to that user. Sending email function etc. are all ready. I am not getting as to how I can customize this admin template to add a send button. Can someone help ?? or atleast point me in the right direction...
P.S: it need not be a send button, it could be part of "action" dropdown where for the selected users I can jointly send emails.
Regarding the send button for each row, you can give your model (or ModelAdmin) a new function which returns the corresponding HTML pointing to your views (or calling corresponding AJAX functions). Just add your function to the ModelAdmin's "list_display" and make sure that HTML tags don't get escaped:
class MyModelAdmin(admin.ModelAdmin):
...
list_display = ('name', 'email', 'sender', 'send_email_html')
def send_email_html(self, obj):
# example using a javascript function send_email()
return 'Send Now' % obj.id
send_email_html.short_description = 'Send Email'
send_email_html.allow_tags = True
Regarding the use of an action, define "actions" in your ModelAdmin as a list containing your function which takes modeladmin, request, queryset as parameters:
def send_email_action(modeladmin, request, queryset):
whatever_you_want_to_do_with_request_and_queryset
send_email.short_description = 'Send email'
class MyModelAdmin(admin.ModelAdmin):
...
actions = [
send_email_action
]
My solution below is for adding the "send invite" action in admin interface
"Send Invite" action
You can refer to the django admin-actions documentation here.
Here is what your admin.py should look like:
from django.contrib import admin
from myapp.models import MyModel
from django.core.mail import send_mail
class MyModelAdmin(admin.ModelAdmin):
actions = ['send_invite']
def send_invite(self, request, queryset):
# the below can be modified according to your application.
# queryset will hold the instances of your model
for profile in queryset:
send_email(subject="Invite", message="Hello", from_eamil='myemail#mydomain.com', recipient_list=[profile.email]) # use your email function here
send_invite.short_description = "Send invitation"
admin.site.register(MyModel, MyModelAdmin)
I have not tested this code, but it is pretty much what you need. Hope this helps.