I have the following urls in my Django application:
path('rooms/<room_id>',views.home,name='home'),
models:
class ChatRoom(models.Model):
eid = models.CharField(max_length=64, unique=True)
name = models.CharField(max_length=25)
views
def rooms(request):
room = UserProfile.objects.filter(user=request.user).values()[0]['room_id']
rooms = ChatRoom.objects.all().values()
user = User.objects.filter(username=request.user)
return render(request,'chat/rooms.html',{'rooms':rooms,'room_user':room})
Here <room_id> is variable i.e it depends on the eid of a Room model. A user can be a part of only one room. Therefore, the user can access only one <room_id>, let us say '4'. So, a user can access only rooms/4/. How can I restrict the user from entering into other URLs e.g. /rooms/5/ ?.
You might want to make a ForeignKey from UserProfile to the ChatRoom model:
class UserProfile(models.Model):
room = models.ForeignKey(ChatRoom, on_delete=models.PROTECT)
# …
Then you can filter the rooms to only allow the one by the user with:
from django.contrib.auth.decorators import login_required
#login_required
def rooms(request):
# the ChatRoom or None
room = ChatRoom.objects.filter(userprofile__user=request.user).first()
return render(request,'chat/rooms.html',{'room': room})
Since a user can only belong to one room as you say, it is thus a single room.
In your home method, we can use get_object_or_404(…) [Django-doc] to raise a 404 in case the room is not the one of the user:
from django.contrib.auth.decorators import login_required
from django.shortcuts import get_object_or_404
#login_required
def home(request, room_id):
room = get_object_or_404(ChatRoom, eid=room_id, userprofile__user=request.user)
# …
That being said, if a user can only be a member of one ChatRoom, then it makes not much sense to include this in the URL. You can simply obtain the chatroom as displayed above.
Note: You can limit views to a view to authenticated users with the
#login_required decorator [Django-doc].
Related
I have limited the amount of files a user can upload to my site using a logical query in my upload app's views.py file. Long story short, after the user has uploaded 5 files, they are redirected to a page that says they must become a premium member.
I am now trying to add to that logic by checking if they are in the "Free User" group. If they are not, they should be allowed to upload an unlimited number of files.
So far I have used the admin panel to create two groups. One is "Free User" and one is "Gold User".
I gave both groups add, change, delete, view permissions for my "beatupload" app.
I added this code to my users models.py
# users/models.py
from django.contrib.auth.models import AbstractUser, Group
from django.db import models
class CustomUser(AbstractUser):
pass
# add additional fields in here
group = models.ForeignKey(Group, on_delete=models.CASCADE, default=1)
def __str__(self):
return self.email
I see that I have a field in my users_customuser table for group_id, but when I change the group in the admin interface no changes reflect.
When I check the table using select * from auth_group I see that I have id and group name, being Free User and Gold User.
I am trying to use this query in my upload views.py:
class uploadNew(CreateView): # new
model = beat
fields = ['title', 'beat']
success_url = reverse_lazy('uploads')
#Check number of beats uploaded by user and if exceeds require signup
def get_template_names(self):
if (beat.objects.filter(producer=self.request.user).count() <= 4 and user.groups.filter(name='Free User').exists()):
return ['uploadNew.html',]
else:
return ['becomeMember.html',]
# END CHECK #
def form_valid(self, form):
form.instance.producer = self.request.user
return super(uploadNew, self).form_valid(form)
So somehow I am not linking my custom user to the group correctly, and there is no checking what group the user is in to either allow unlimited uploads or require becoming a Gold User.
Please let me know if you need to see more code, and also explain for a beginner if possible, noting what areas I may be weak in and need to read more of in Django's documentation. I am on 2.2.2 by the way.
After creating the groups in admin panel and verifying that a relationship exists when executing
SELECT * from users_customuser_groups;
I was able to get my intended results with the following code:
if (beat.objects.filter(producer=self.request.user).count() == 5 and self.request.user.groups.filter(name="Free User").exists()):
My full models code is posted below for reference. The app name is 'users'
# users/models.py
from django.contrib.auth.models import AbstractUser, Group
from django.db import models
class CustomUser(AbstractUser):
pass
# add additional fields in here
group = models.ForeignKey(Group, on_delete=models.CASCADE, default=1)
def __str__(self):
return self.email
VIEWS:
#beatupload/views.py
from django.shortcuts import render
from django.views.generic import ListView, CreateView
from django.urls import reverse_lazy
from .forms import beatUploadForm #new
from .models import beat
# Create your views here.
class UploadView(ListView):
model = beat
template_name = 'uploads.html'
def get_queryset(self):
return beat.objects.filter(producer=self.request.user)
class uploadNew(CreateView): # new
model = beat
fields = ['title', 'beat']
success_url = reverse_lazy('uploads')
#Check number of beats uploaded by user and if exceeds amount require signup
#To render sign up template if true and proceed to upload if false
def get_template_names(self):
if (beat.objects.filter(producer=self.request.user).count() == 5 and self.request.user.groups.filter(name="Free User").exists()):
return ['becomeMember.html',]
else:
return ['uploadNew.html',]
# END CHECK #
def form_valid(self, form):
form.instance.producer = self.request.user
return super(uploadNew, self).form_valid(form)
I am trying to figure out how to customize the django LoginView based on whether or not it's the user's first time logging on for the day. I have my LoginView currently set up so that it defaults to the LOGIN_REDIRECT_URL = "book:author" in my settings.py file. This works flawlessly. When a user logins in and is successfully authenticated, they are redirected to "book:author" as I would expect.
What I'm trying to do is if this is the first time the user has logged in for the day, direct them to one URL, and if it's any other login iteration for the day, redirect them to a different URL. I have read about various methods on how to do this, to use messaging as opposed to conditional URL redirect to using the NEXT parameter and I'm trying to figure out which is the best and most secure and proper way of going about this.
Here is my default LoginView...( Nothing fancy )
class LoginView(LoginView):
template_name = 'registration/login.html'
form_class = AuthenticationForm
And then it redirects based on my settings.py file definition...
LOGIN_REDIRECT_URL = "book:author"
What is the best way to redirect for first login of the day to different URL?
Thanks in advance for any suggestions.
I found this SO answer Django -- Conditional Login Redirect and it seems to be what I'm looking for. Are there any downsides to using the example at the bottom?
And how to do with a LoginView as opposed to the function based example?
To answer your question, let me suppose you have a client model, in a way, similar to this model, and we'll need a helper models that stores the user's logins:
models.py:
from django.db import models
from django.contrib.auth.models import User
class Client(models.Model):
"""
This client model is pointing to django's User model
You can use your custom user model using AbstractBaseUser or whatever.
And if you're using django's User model directly, this class is not required
"""
user = models.OneToOneField(
User,
on_delete=models.DO_NOTHING,
verbose_name='User',
related_name='cli', # related name manager if needed
null=False,
blank=False,
)
def __str__(self):
return '{}'.format(self.user.username)
class ClientLogins(models.Model):
"""
This is a helper model table that stores the logins dates of the client
"""
client = models.ForeignKey(
Client,
verbose_name='Client',
on_delete=models.DO_NOTHING
)
date = models.DateTimeField(verbose_name="Date login")
def __str__(self):
return '{}'.format(self.client)
Then your form:
forms.py:
class LoginForm(forms.ModelForm):
'''Simple login form'''
class Meta:
model = User
fields = ('username', 'password')
And finally, your login behaviour should be treated in the views classes/functions.
views.py:
from datetime import timedelta
from django.utils import timezone
from MY_APP import models, forms
class LoginUser(LoginView):
template_name = 'login.html' # your template
from_class = forms.LoginForm # your form
def get_success_url(self):
'''Here the part where you can implement your login logic'''
now = timezone.now()
# Get current day date object
# like: 12/02/2019 00:00:00
today = now.replace(minute=0).replace(second=0).replace(microsecond=0)
# Get the client from the user object
client = self.request.user.cli
# Get all the user today's logins and count them
client_logins = models.ClientLogins.objects.filter(
client=client,
date__gte=today,
date__lte=today + timedelta(days=1)
).count()
if client_logins < 1: # Or: if not client_logins:
# create a login tracker record
models.ClientLogins.objects.create(
client=client,
date=now # Store the date where the user logged in the website
)
return reverse_lazy('FIRST_LOGIN_REDIRECT_URL')
# Or redirect to: settings.LOGIN_REDIRECT_URL
return super().get_success_url()
And for more informations, this is the core code of LoginView, like this you can know the MRO list and what you can override to have your desired behaviour.
I am currently building a small project using Django, I have noticed a problem that a logged in user was getting access to the other users page by simply changing the id in the url i.e
This is the url of currently logged in user
http://localhost:8000/home/myBooks/7/
by changing that id from 7 to 6
i.e
http://localhost:8000/home/myBooks/6/
He was getting access to that page,I have used #login_required for functional based views and LoginRequiredMixin for class based views ,but they are not helping, what else I need to do to prevent this problem?
My app/views.py:
from django.shortcuts import render,redirect
from django.http import HttpResponse
from django.views.generic.edit import FormView
from . forms import BookForm
from django.contrib.auth.models import User
from . models import UserBooks
from django.contrib.auth.models import User
from django.views import generic
from django.contrib.auth.decorators import login_required
from .models import UserBooks
from django.shortcuts import get_object_or_404
from django.contrib.auth.mixins import LoginRequiredMixin
#login_required
def HomeView(request):
return render(request,'home/homepage.html')
class BookDetailsView (LoginRequiredMixin,generic.DetailView):
model=UserBooks
template_name='home/bookdetails.html'
class BooksView (LoginRequiredMixin,generic.DetailView):
model=User
template_name='home/mybooks.html'
#login_required
def addBooks(request):
if (request.method=='POST'):
form=BookForm(data=request.POST)
if(form.is_valid()):
u=UserBooks()
u.book_name=form.cleaned_data['book_name']
u.book_author=form.cleaned_data['book_author']
u.book_ISBN=form.cleaned_data['book_ISBN']
u.book_status=True
u.book_genre=form.cleaned_data['book_genre']
u.username=request.user.username
u.user_id = User.objects.get(username=request.user.username)
u.save()
return redirect('/')
else:
form = BookForm()
return render (request,'home/addbooks.html',{'form':form})
my apps/models.py:
from django.db import models
from django.contrib.auth.models import User
class UserBooks(models.Model):
user_id = models.ForeignKey(User,on_delete=models.CASCADE,null=True)
username = models.CharField(max_length=200)
book_name = models.CharField(max_length=200)
book_author = models.CharField(max_length=200)
book_ISBN=models.CharField(max_length=200)
book_genre = models.CharField(max_length=200)
book_status=models.BooleanField(default=False)
class Meta:
unique_together = (("username", "book_ISBN"),)
def __str__(self):
return self.book_name
my apps/urls.py:
from django.urls import path
from . import views
app_name='home'
urlpatterns=[
path('',views.HomeView,name='home'),
path('addBooks/',views.addBooks,name='addBooks'),
path('myBooks/<int:pk>/',views.BooksView.as_view(),name='myBooks'),
path('<int:pk>/', views.BookDetailsView.as_view(), name='myBooks'),
]
If your view should always show the detail for the current user, don't put the ID in the URL at all; get the logged-in user directly within the view.
class BooksView(LoginRequiredMixin, generic.DetailView):
model = User
template_name ='home/mybooks.html'
def get_object(self):
return self.request.user
...
path('myBooks/',views.BooksView.as_view(),name='myBooks'),
class BooksView(LoginRequiredMixin, DetailView):
...
def get(self, request, *args, **kwargs):
current_user = User.objects.get(id=self.request.user.pk)
if current_user.pk == kwargs['pk']:
return HttpResponseRedirect('/')
else:
return HttpResponseRedirect('profile-url')
Here I assume that if you are logged in user and you try to check another user profile by giving id in url. So I add a get method which will check is requested URL id is for the current user (books/7/ is 7 is current user id) if not then redirect to an URL, for example, otherwise redirects to another url. You can get some idea. This may not help you exactly.
If you have just started to develop the app, then it is okay to use pks inside of urls. However, when it comes to a real working app it can lead to some security problems.
As you wrote, one can simply change the url and get some private data.
Other problems can be:
The number of users in the database can be easily counted by iteration through your urls.
The user can be easily detected by his id. Knowing it one can easily get some private data.
If you will decide to change ids in your db, then all the external links will be broken...and etc.
Considering that I suggest an approach in which you use ids internally. For external usage (urls, links) you can use uuids.
To do that you just need additional field into your model:
import uuid
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
And here is the example url:
url(r'^myBooks/(?P<user_uuid>\b[0-9A-Fa-f]{8}\b(-\b[0-9A-Fa-f]{4}\b){3}-\b[0-9A-Fa-f]{12}\b)/$',
After you switch to uuids it will be almost impossible to "hack" the url.
I'm a total newbie to django so this may well have an obvious answer but so far google hasn't worked out for me.
I have this skeleton application using Django 1.8.
I have a simple model that has an owner field which is a ForeignKey to Group.
When a user is logged in I would like to show only the items that he/she has access to. Access being determined by the fact that the user belongs to the same group.
model.py
class Device(models.Model):
name = models.CharField(max_length=100,db_index=True)
owner = models.ForeignKey(Group)
def __str__(self):
return self.name
views.py
from django.contrib.auth.decorators import login_required
from django.utils.decorators import method_decorator
from django.views import generic
from .models import Device
from django.contrib.auth.models import Group, User
class IndexView(generic.ListView):
"""
This renders the index page listing the devices a user can view
"""
template_name = 'devices/index.html'
context_object_name = 'devices_list'
#method_decorator(login_required)
def dispatch(self, *args, **kwargs):
return super(IndexView, self).dispatch(*args, **kwargs)
def get_queryset(self):
"""
Return the devices visible to the logged-in user
"""
return devices=Device.objects.all()
What I don't seem to be able to figure out is what to put in the .filter() instead of .all() call in my get_queryset method.
Updated based on Jean-Michel's feedback.
I don't have a Django environment in front of me at the moment, but this might be a good start:
return devices=Device.objects.filter(owner=self.request.user.groups.all())
Alternatively, Django's ORM uses double underscore (__) to access field lookups. These can be used to get values greater than (__gt), or in a list (__in) amongst other lookups (see the docs).
return devices=Device.objects.filter(owner__in=self.request.user.groups.all())
This kind of depends on where the user object is located. I'm assuming the logged in user is kept as a class attribute, i.e., self.user. Per, Jean-Michel's comments, the user object is attached to the request. So we can access it from self.request.user.groups.
Finally, you can access specific fields on models using the double underscore notation as well (__), this example is from the docs:
# Find all Articles for any Reporter whose first name is "John".
>>> Article.objects.filter(reporter__first_name='John')
[<Article: John's second story>, <Article: This is a test>]
we are running multiple django sites (let's call them site1, site2, site3) against the same database, and we'd like to permit duplicated usernames accross them.
Site and auth framework do not seem to achieve this, by default, username is a unique field in auth.User.
So what I've done so far (monkey patch, messing up with the user object...):
User._meta.get_field('username')._unique = False
User.add_to_class('site', models.ForeignKey(Site, default=Site.objects.get_current().id, blank=True, null=True))
User._meta.unique_together = (('username', 'site'),)
This piece removes the uniqueness of username, add a site field, make the couple (username, site) unique.
Then come problems which could occure when requesting a User.objects.get(username=xx) (e.g., authentication backends), if some users have the same username on different site.
So, I decided to patch the User.objects manager:
def get_query_set(filter=True):
q = QuerySet(User.objects.model, using=User.objects._db)
if filter:
return q.filter(site = Site.objects.get_current())
return q
User.objects.get_query_set = get_query_set
Seems to work so far. But... the sites use pretty much the same objects, and it's all likely we change user field of these objects using the admin interface, which is common to all sites... hence, if I want to attribute an object (which has a foreignkey to auh.User) to a user of site2 while being logged in as admin on site1, that won't work, as the user manager will filter on site=site1.
I digged up a little, found that this seems to work:
class UserDefaultManager(UserManager):
def get_query_set(self, filter=None):
return QuerySet(User.objects.model)
User._default_manager = UserDefaultManager()
As far as I understand, _default_manager is used by the related objects manager.
Then, User.objects.get(username=xx) filter on sites, and an_object.user won't.
Well, question is: yes, this is messy, and I'm pretty sure there will be flaws, but which are they ?
Next question is: if it's valid, then where is the best place to put this piece of code ? It's currently in a models.py file, just ran as the module is loaded...
Instead of this I propose to use a profile :
models.py:
from django.contrib.auth.models import User
class UserProfile(models.Model):
""" Modèle ajoutant des propriété au modèle User """
user = models.OneToOneField(User, editable=False)
site1 = models.BooleanField()
site2 = models.BooleanField()
site3 = models.BooleanField()
def create_user_profile(sender, instance, created, **kwargs):
""" Crée la jonction entre le modèle User, et le modèle UserProfile """
if created:
UserProfile.objects.create(user=instance)
post_save.connect(create_user_profile, sender=User)
and on each site you create a decorator :
decorators.py:
try:
from functools import wraps
except ImportError:
from django.utils.functional import wraps
from django.http import HttpResponseForbidden
from django.contrib.auth.decorators import login_required
from distrib.views.error import error403
def site1_required(function):
#wraps(function)
#login_required
def decorateur(request, *k, **a):
if request.user.get_profile().site1 or request.user.is_superuser:
return function(request, *k, **a)
else:
result = error403(request)
return HttpResponseForbidden(result)
return decorateur
return function
then on each view you add the decorator, if the user is not allowed to connect on this site, he will get a http403 error.