Show error message when decorator fails - django

The decorator is working fine but I would like to display an error message (I'd like to use messages framework) if the user doesn't belong to any of the required groups. Here's the decorator:
def group_required(*group_names):
"""Requires user membership in at least one of the groups passed in."""
def in_groups(user):
if user.is_authenticated():
if bool(user.groups.filter(name__in=group_names)) or user.is_superuser:
return True
return False
return user_passes_test(in_groups)
I call it using something like:
#require_http_methods(['GET'])
#group_required('supervisor')
def home_view(request):
return render(request, 'home.html')
I tried using this snippet to use messages framework (since this requires the request object) but it realized that messages framework middleware didn't appear installed inside the decorator.
I'm willing to change whatever it takes :)
Update:
What I'm looking for:
def group_required(request, *group_names):
"""Requires user membership in at least one of the groups passed in."""
def in_groups(user):
if user.is_authenticated():
if user.groups.filter(name__in=group_names).exists() or user.is_superuser:
return True
else:
# I'm getting:
# You cannot add messages without installing django.contrib.messages.middleware.MessageMiddleware
messages.add_message(request, messages.ERROR, 'Group is not allowed')
return False
return user_passes_test(in_groups, request)

I don't think you really need threadlocals in this use case. And normally when threadlocals seems to be the only way to go in a Django app, there could be some mis-structured context layers. Comparing w/ the threadlocals, I would rather to duplicate user_passes_test and then modify it to pass request to in_groups (We could not pass request to is_group easily without modifying the code of user_passes_test. Check the question: How to pass Django request object in user_passes_test decorator callable function.) (Maybe a ticket for this?)
Furthermore, bool(user.groups.filter(name__in=group_names)) would caused items to be retrieved to DB adapter and Python instance before deciding the existence, using exists() and thus user.groups.filter(name__in=group_names).exists() to directly return bool result from DB backend is far more efficient here.

Related

Rejecting form post after is_valid

I'm attempting to overwrite the RegistrationView that is part of django registration redux.
https://github.com/macropin/django-registration/blob/master/registration/backends/default/views.py
Essentially, I want to implement logic that prevents the registration from happening if some condition is present. Checking for this condition requires access to the request.
I'm thinking that I might be able to subclass the register function within this Class based view.
def register(self, form):
#START OF MY CODE
result = test_for_condition(self.request)
if result == False:
messages.error(self.request, "Registration cannot be completed.", extra_tags='errortag')
return redirect('/access/register')
#END OF MY CODE
site = get_current_site(self.request)
if hasattr(form, 'save'):
new_user_instance = form.save(commit=False)
else:
new_user_instance = (UserModel().objects
.create_user(**form.cleaned_data))
new_user = self.registration_profile.objects.create_inactive_user(
new_user=new_user_instance,
site=site,
send_email=self.SEND_ACTIVATION_EMAIL,
request=self.request,
)
signals.user_registered.send(sender=self.__class__,
user=new_user,
request=self.request)
return new_user
What is the correct way to do this using class based views? I've worked almost exclusively with function based views so far.
Thanks!
As per my knowledge, it is recommended to subclass "AsbtractBaseUser" and "BaseUserManager" for user management in Django.
In CustomUserModel, you can define required fields added to the existing fields from BaseUserManager.
In CustomUserManager, you can override create_user, create_staff_user, create_superuser, and save_user. You can define a few conditions that should be checked before registering the user and also you can add permissions or groups to the user based on the role in the "save_user" method.
Create CustomRegistrationSerializer, that controls the fields that need to be displayed on the registration page, and validation of those fields can also be defined.
In POST method of CBV, you can check condition that requires "request" before serializing data. If condition is met, you can continue with serializing and saving otherwise, you can return appropriate response.
Django : https://docs.djangoproject.com/en/3.2/topics/auth/customizing/#extending-the-existing-user-model

How to safely access request object in Django models

What I am trying to do:
I am trying to access request object in my django models so that I can get the currently logged in user with request.user.
What I have tried:
I found a hack on this site. But someone in the comments pointed out not to do it when in production.
I also tried to override model's __init__ method just like mentioned in this post. But I got an AttributeError: 'RelatedManager' object has no attribute 'request'
Models.py:
class TestManager(models.Manager):
def user_test(self):
return self.filter(user=self.request.user, viewed=False)
class Test(models.Model):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request', None)
super(Test, self).__init__(*args, **kwargs)
user = models.ForeignKey(User, related_name='test')
viewed = models.BooleanField(default=False)
objects = TestManager()
I trying to access request object in my Django models so that I can get the currently logged in user with request.user.
Well a problem is that models are not per se used in the context of a request. One for example frequently defines custom commands to do bookkeeping, or one can define an API where for example the user is not present. The idea of the Django approach is that models should not be request-aware. Models define the "business logic" layer: the models define entities and how they interact. By not respecting these layers, one makes the application vulnerable for a lot of problems.
The blog you refer to aims to create what they call a global state (which is a severe anti-patten): you save the request in the middleware when the view makes a call, such that you can then fetch that object in the model layer. There are some problems with this approach: first of all, like already said, not all use cases are views, and thus not all use cases pass through the middleware. It is thus possible that the attribute does not exist when fetching it.
Furthermore it is not guaranteed that the request object is indeed the request object of the view. It is for example possible that we use the model layer with a command that thus does not pass through the middleware, in which case we should use the previous view request (so potentially with a different user). If the server processes multiple requests concurrently, it is also possible that a view will see a request that arrived a few nanoseconds later, and thus again take the wrong user. It is also possible that the authentication middleware is conditional, and thus that not all requests have a user attribute. In short there are more than enough scenario's where this can fail, and the results can be severe: people seeing, editing, or deleting data that they do not "own" (have no permission to view, edit, or delete).
You thus will need to pass the request, or user object to the user_test method. For example with:
from django.http import HttpRequest
class TestManager(models.Manager):
def user_test(self, request_or_user):
if isinstance(request_or_user, HttpRequest):
return self.filter(user=request_or_user.user, viewed=False)
else:
return self.filter(user=request_or_user, viewed=False)
one thus has to pass the request object from the view to the function. Even this is not really pure. A real pure approach would only accept a user object:
class TestManager(models.Manager):
def user_test(self, user):
return self.filter(user=user, viewed=False)
So in a view one can use this as:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
# return Http response
For example if we want to render a template with this queryset, we can pass it like:
def some_view(request):
some_tests = Test.objects.user_test(request.user)
# ...
return render(request, 'my_template.html', {'some_tests': some_tests})

Create a User Profile or other Django Object automatically

I have setup a basic Django site and have added login to the site. Additionally, I have created a Student (Profile) model that expands upon the built in User one. It has a OneToOne relationship with the User Model.
However, I have yet not come right with forcing the user to automatically create a Profile the first time they log in. How would I make sure they are not able to progress through anything without creating this?
I have tried by defining the following in views:
def UserCheck(request):
current_user = request.user
# Check for or create a Student; set default account type here
try:
profile = Student.objects.get(user = request.user)
if profile == None:
return redirect('/student/profile/update')
return True
except:
return redirect('/student/profile/update')
And thereafter adding the following:
UserCheck(request)
at the top of each of my views. This however, never seems to redirect a user to create a profile.
Is there a best way to ensure that the User is forced to create a profile object above?
Looks like you're attempting to do something similar to Django's user_passes_test decorator (documentation). You can turn the function you have into this:
# Side note: Classes are CamelCase, not functions
def user_check(user):
# Simpler way of seeing if the profile exists
profile_exists = Student.objects.filter(user=user).exists()
if profile_exists:
# The user can continue
return True
else:
# If they don't, they need to be sent elsewhere
return False
Then, you can add a decorator to your views:
from django.contrib.auth.decorators import user_passes_test
# Login URL is where they will be sent if user_check returns False
#user_passes_test(user_check, login_url='/student/profile/update')
def some_view(request):
# Do stuff here
pass

Is there a way to remove hyperlinks from objects on django admin changelist that user does not have permission to change?

I am currently utilizing the has_change_permission hook in my custom django admin class to implement a simple form of row-level permissions and determine whether a non-superuser can edit a particular object, like so:
def has_change_permission(self, request, obj=None):
if obj is None or request.user.is_superuser or (obj and not obj.superuser_only): # (my model has a 'superuser_only' flag that gets set via fixtures, but its beyond the scope of this question)
return True
return False
This works well enough: all objects are shown to a user, but if they click on an object that they don't have permission to edit then they are taken to my 403 page, presumably because PermissionDenied is raised. However, giving them a hyperlink to a permission denied page doesn't seem ideal for this case; I would like to show the objects but not provide any hyperlinks to the edit page on the list page (in addition to raising PermissionDenied if they tried to manually use the URL for the object). Is there a straightforward hook for removing these hyperlinks without a horrendous hack? I'm an experienced django developer so if you can point me in the right direction (if any exists), I'll be able to implement the details or determine that its not worth it for now.
I was able to accomplish this in a fairly straightforward way by overwriting the default get_list_display_links function in the ModelAdmin class (in my admin.py file):
def get_list_display_links(self, request, list_display):
if request.user.is_superuser:
if self.list_display_links or not list_display:
return self.list_display_links
else:
# Use only the first item in list_display as link
return list(list_display)[:1]
else:
# Ensures that no hyperlinks appear in the change list for non-superusers
self.list_display_links = (None, )
return self.list_display_links
Note that my code maintains the hyperlink for superusers. You can easily just use the latter part of the function if you don't want to make this distinction.

django test client gets 404 for all urls

I am doing my first experiments with django testing and I am having the problem that I always get the 404 template regardless which url (even /) I am using.
If I throw the very same code into the django shell it's working as expected and always presents me the contents of the requested url.
class SimpleTest(TestCase):
def setUp(self):
self.user = User.objects.create_user('test', 'test', 'test')
self.user.is_staff = True
self.user.save()
self.client = Client()
def test_something(self):
self.assertTrue(self.client.login(username='test', password= 'test'))
self.client.get("/")
The login returns True, but the get() fails. Any hints what I am doing wrong here?
Keep in mind that most views use something like get_object_or_404, get_list_or_404, or simply raise Http404 when there's a problem accessing some object or another. You'll need to make sure that your test database is populated with sufficient objects to fulfill all these requirements to make the view not return a 404.
Remember, when running tests, the database is rolled back after each test (using transactions), so each test method must stand on its own or the setUp method must populate the database with any required dependencies.