Django - Using defaults if query fails - django

I am trying to create a script which will retrieve a user account and at the same time that particular users account 'theme' in one simple query. Here is the code I am using, please note the get_account method:
class AccountManager(Manager):
def __init__(self, *args, **kwargs):
super(AccountManager, self).__init__(*args, **kwargs)
def get_account(self, slug):
return get_object_or_404(self.select_related('theme'), status__exact=self.model.ACTIVE_STATUS, slug__exact=slug)
It works nicely, but if for whatever reason the 'theme' object returns null (this should never happen but I want to be sure!), it will redirect to a 404 page. What I want it to do is check if the theme object exists and is valid, and if not, default to the standard theme and carry on.
I would still like the query to throw a 404 if the user account is not valid though. Short of using a bucket load of try/exceptions, is there a nice clean way of doing this? I like to keep my Managers nice and tidy :)
Hope someone can help

try:
account = self.select_related('theme').get(
status__exact=self.model.ACTIVE_STATUS, slug__exact=slug
)
except self.model.DoesNotExist:
account = self.select_related('theme').get(slug="default_slug")
return account

In your example, it wouldn't result in a 404 if the related theme was null as it is not the object being retrieved with get_object_or_404(). It will only 404 if it can't find an Account object that is both ACTIVE_STATUS and a matching slug.
My suggestion to ensure you use a default theme with any valid Account object is:
class AccountManager(Manager):
def __init__(self, *args, **kwargs):
super(AccountManager, self).__init__(*args, **kwargs)
def get_account(self, slug):
account = get_object_or_404(self.select_related('theme'), status__exact=self.model.ACTIVE_STATUS, slug__exact=slug)
if not account.theme:
account.theme = default_theme_obj
return account
I'll leave it up to you to determine how you would get default_theme_obj as I don't know your models, caching structure and whatnot.
In the interests of keeping your managers tidy, you don't need the __init__ method either as it doesn't do anything that the default one wouldn't do.

Related

How to access to request.user in def save() in models?

I want to create a user at the creation of an object. This object is linked to the user by a foreign key.
I have override the def_save() method to create the user and link it to the object.
Problem: I generate a random password for this user and I would like to send it by e-mail not to the just created user but to the user.
def save(self, *args, **kwargs):
if self._state.adding:
super(Machine, self).save(*args, **kwargs)
username = f"machine_{slugify(self.site.client.name).lower()}_{self.id}"
password = User.objects.make_random_password()
self.user = User.objects.create(
username=username,
password=password
)
self.save(update_fields=['user'])
send_mail(
f'Password of {username}',
f'Password: {password}',
settings.DEFAULT_FROM_EMAIL,
[self.request.user.email],
fail_silently=True,
)
else:
super(Machine, self).save(*args, **kwargs)
The problem is that I don't have access to self.request in this method.
How can I access to request in my def save()?
Or how can I get the password value in my view?
I think you should design this differently.
If there is always a view, it suggests that the only legitimate place this object and the related user could be created is inside a particular view. So, use get_or_create and if it was created, then invoke the logic to create and associate the new user and e-mail the password to the current Django user.
You could harden it against object creation outside of an appropriate view by instead using
try:
existing_instance = MyModel.objects.get( ...)
except MyModel.DoesNotExist
new = MyModel( ...)
# create and associate the User object here
setattr( new, '_foo_bar', 'not_Molly') # a Mollyguard
new.save()
and check in MyModel's save method that self._foo_bar is present and correct. Raise a meaningful error if not. This will avoid accidental creation of MyModel instances without an associated User by, say, newly recruited help who don't fully understand the bad implications of doing this.
If you really, really want, you could pass the current request.User as the value of an attribute, and check isinstance( self._foo_bar, User) and then having crashed out if you don't have a valid User, put the logic in the save method. This feels wrong to me.
To answer your question directly (I definitely think you should read the design suggestions here also) but to get the request object throughout the request cycle, one solution is threadlocals. Threadlocals middleware puts the request object on a thread-accessible storage, and then provides a get_current_request handler that you can import anywhere and grab the request off of local storage.
So many caveats here: Django core devs intentionally didn't include this functionality, here is a great discussion of why you shouldn't do this, Python is not 100% thread safe, this may be (and probably is) an anti-pattern, and consider the cases brought up in this thread.

Restric a user to get or update a object made by specific user django

I have a task model like
class Tasks(models.Model):
made_by=models.ForeignKey(User , on_delete=models.CASCADE)
title = models.CharField(max_length=100,null=True,blank=True)
I have a view like
def get(self, request, id):
task = Tasks.objects.get(id=id)
if task:
serializer = TasksSerializer(task, many=False)
return Response(success_response(serializer.data, "Contact Information."
status=status.HTTP_200_OK)
And a PUT in same way. I want to check Only User which made_by it can access it . Is there any smart way to do this ? I dont want to query for check again in all views and hereafter.
Since it appears that you are using class-based views I would suggest that you override the dispatch method of your class. This class gets executed every time someone calls the view, no matter the method.
In this dispatch method you could first of all retrieve the task object like you do in the get-function in your current code. After that step you could then perform a check to see whether the request.user equals the object's made_by.
For example:
def dispatch(self, request, id):
self.task = Tasks.objects.get(id=id) # consider using get_object_or_404
# Check if user is owner of task, otherwise throw a 404
if request.user != self.task.made_by:
raise Http404()
# Will continue execution as normal, calling get() if a get-request was made
# the variable self.task will be available in this function, so re-retrieving the
# object is not necessary
return super().dispatch(request, id)
Additionally I would also suggest using the default LoginRequiredMixin (source) to make sure that only logged-in users can access the view. It could eliminate custom written checks in many cases.
The PermissionRequiredMixin (source) is also a great choice when dealing with more general permissions that are not related to specific instances.
For more specific - customized - permissions you could also use the UserPassesTestMixin (source) to write checks in dedicated test funcs to keep your code cleaner.
a couple different ways to go about this but I think the easiest is to test the user's made by status. i.e.
def run_task(self, request, id):
if request.user.made_by == 'Foo Bar'
. . . .
return Response(success_response ...
else
return Response(failure_response ...
another, more complicated way would be to play around with the standard account model perms and set 'task' perms to false. i.e.
def has_task_perms(self):
return False
and then in the process of user creation through whichever user type has the ability to give task perms. The most common way I am aware of to do this is to use an account manager (a whole rabbit whole in and of itself.) i.e.
class AccountManager(BaseUserManager):
def create_user(self, password):
# define regular creation process
. . .
return user
def create_special_user(self, password):
# define special creation process i.e.
has_task_perms == True
return user
I tried so many solution but At the End I made a decorator like this
def task_ownership_check(func):
def wrapper(request,*args, **kwargs):
print(kwargs['id'])
try:
task = Tasks.objects.get(id=kwargs['id'])
except Tasks.DoesNotExist:
return Response(failure_response(data={}, msg='No task matching query found'),
status=status.HTTP_404_NOT_FOUND)
if args[0].user != task.todolist.for_user:
return Response(failure_response(data={'error': 'You are not allowed to access this record'},
msg='You are not allowed to access this record'),
status=status.HTTP_400_BAD_REQUEST)
else:
return func(args[0], kwargs['id'])
return wrapper
So Now I can check easily by including #task_ownership_check on any task

Move POST parameters to query parameters before processing request

I need to move the request.POST parameters to the request.query_params QueryDict.
Is there an accepted way of doing this?
Background
I am using datatables, with a DRF backend, which is working fine. I am moving the application to integration and ... it stops working. Why? Request URL too big (on the 7000 characters range) - which was not a problem in my dev host ...
So, I am looking for a solution to that problem. The first solution is to use POST instead of GET. That works, but the library integrating DRF with datatables is not processing the form parameters of the POST request. Because of that, filtering, pagination and so on have stopped working.
The easiest thing to solve this would be to put the form parameters into the query parameters, and let the backend process the request as if it was a normal GET request.
This is what I am doing at the moment:
class DataViewSet(viewsets.ModelViewSet):
queryset = Data.objects.all()
serializer_class = DataSerializer
def create(self, request, *args, **kwargs):
# DataTable uses a lot of parameters which do not fit into a normal URL. To get the data we need to do POST,
# so that the parameters are sent in the body
# We hijack the create method to list the data
return self.list(request, *args, **kwargs)
I'm not aware of any accepted ways of doing this. But let me offer you an idea. It is probably on the opposite side of what accepted means.
The rest_framework.request.Request.query_params look like this:
#property
def query_params(self):
return self._request.GET
Im thinking about substituting the self._request.GET with self._request.POST
class DataViewSet(viewsets.ModelViewSet):
queryset = Data.objects.all()
serializer_class = DataSerializer
def create(self, request, *args, **kwargs):
# DataTable uses a lot of parameters which do not fit into a normal URL. To get the data we need to do POST,
# so that the parameters are sent in the body
# We hijack the create method to list the data
request._request.GET = request._request.POST
return self.list(request, *args, **kwargs)
This should work for POST data. Sending files to this endpoint is probably bad idea.
NOTE: This is very fishy and could introduce bugs in the future. Without look into your code i cannot predict the side effects.

Overriding a django model default value in the default form?

I have a model that looks something like:
class DooDad(models.Model):
doo_dad_dogue = models.BooleanField(default=True)
Trouble is, that default needs to be manipulated by... stuff that is irrelevant to this question. If I were creating the form that creates the object, the solution would be trivial. I'm still using the django default form for creating these things, though, and I'd rather keep it that way.
I tried the obvious:
class DooDad(models.Model):
doo_dad_dogue = models.BooleanField(default=True)
def __init__(self, *args, **kwargs):
super(DooDad, self).__init__(*args, **kwargs)
self.doo_dad_dogue = False
...which I suspect would have terrible side effects, but was worth experimenting with. The form still comes up with the box checked.
EDIT: I should have mentioned that this is Django 1.9
If it is not possible to continue using the default model creation form, is there anything unusual that I need to do to to make a ModelForm that only impacts CREATE, and not EDIT?
I do not think using the __init__in model is a good practice. However, if you want to try it is important to know that your code is not correct one the field doo_dad_dogue is a descriptor. The correct way to access it is
using self.fields['doo_dad_dogue'] = False.
Using a form is the correct way to do that. You can override the default value in the Form by using the init method:
def __init__(self, *args, **kwargs):
super(<YOUR_FORM>, self).__init__(*args, **kwargs)
if args is not None and len(args) and args[0] is not None:
<MANIPULATE HERE>
Hope that helps.

Tastypie obj_create - how to use newly created object?

When a new item is created using Tastypie, I want to be able to add it to a user's attribute which is a many-to-many field.
RIght now my obj_create looks like this:
def obj_create(self, bundle, request=None, **kwargs):
return super(GoalResource, self).obj_create(bundle, request, user=request.user)
I want to create the new object, but when I want to be able to add it to the request.user's attribute goal_list. But, what I have will immediately create the object in the database. How would I create the object and then add it to the user's goal_list attribute?
You didn't show us your resource definition, but assuming you are using tastypie.resources.ModelResource as your base class, this should work:
def obj_create(self, bundle, request=None, **kwargs):
bundle = super(GoalResource, self).obj_create(
bundle, request, user=request.user)
user = request.user
user.goals.add( bundle.obj )
user.save()
return bundle
This is because the obj_create method of ModelResource class returns a bundle which contains the saved object (bundle.obj) and you can manipulate this object in your obj_create method as shown and only then return it.
I have also assumed that request.user contains a valid User object (i.e. authenticated), you need to make sure it does for above to work or you should add some error handling code for the case when it does not.
Hope this helps :)
I don't have enough reputation to comment yet so I figured I would put a second answer. The answer above is correct I just wanted to add that request no longer exists in the obj_create call. You can access the current request via bundle.request:
http://django-tastypie.readthedocs.org/en/latest/resources.html#accessing-the-current-request
Thanks for the answer above, it helped me as well!