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.
Related
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].
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 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>]
I am working on a recipe website based on django and have run into a snag on the custom backends.
I am receiving a Validation error -no exception supplied when I try to save my cookbook instance in the backend.
here is my backend:
from registration.backends.default import DefaultBackend
from cookbook.models import Cookbook
from django.contrib.auth.models import User
from registration.models import RegistrationProfile
class RecipeekActivationBackend(DefaultBackend):
def register(self, request, **kwargs):
new_user = super(RecipeekActivationBackend, self).register(request, **kwargs)
new_user.save()
cookbook = Cookbook(name=new_user.first_name, pub_date="12/12/2012", user=new_user)
print"cookbook"
cookbook.save()
return new_user
the error occurs at cookbook.save()
here is my Cookbook model:
class Cookbook(models.Model):
def __unicode__(self):
return self.name
name = models.CharField(max_length=50)
pub_date = models.DateTimeField('date published')
user = models.ForeignKey(User, related_name='cookbooks')
recipes = models.ManyToManyField('Recipe', related_name = 'cookbooks')
I believe that is all i need to supply in order to get a little help.
thank you in advance,
A. Cooper
update: the error was caused by pub_date being passed a string instead of a datetime
update2: the way I am going about this is not the best way and i am now going to attempt to use signals to achieve the same outcome
You're going about this all wrong. Authentication backends are for one thing: authentication. The only reason you should be customizing a backend is if you're trying to tie authentication in from another system or need to make some other change like using email for username. Otherwise, use the defaults
Django provides signals for this exact purpose, so that's what you should use.
from django.db.models.signals import post_save
from django.dispatch import receiver
#receiver(post_save, sender=User)
def create_cookbook_for_user(sender, instance, created, *args, **kwargs):
if created and not instance.cookbooks.exists():
Cookbook.objects.create(name=instance.first_name, pub_date=date.today(), user=instance)
Put that in your models.py, and you're done.
See: https://docs.djangoproject.com/en/dev/topics/signals/
I think the error is with pub_date="12/12/2012": that's not a valid value for DateTimeField. Instead, you want to give it a datetime object: datetime.datetime(2012, 12, 12) (after import datetime).
Or maybe datetime.datetime.now(), or something else based on the actual user.
(Also, maybe this should be a DateField, in which case you want datetime.date(2012, 12, 12) or datetime.date.today(). The datetime object above means midnight on December 12th.)
I don't think you can pass a string to pub_date. Try:
import datetime
pub_date=datetime.datetime.now()