Django passing arguments to custom method in models (or accessing request) - django

I'm trying to implement some customize login through the custom methods in Django's models. I want to know if its possible to:
Get request.user in a custom method
Get the user that made the request in the method
Or pass an argument to the custom method
Thinking in doing something like this:
class OneModel(models.Model):
(...)
def viewed(self):
profile = request.user.profile
viewed = self.viewed_episodes.filter(user=profile).exists()
if viewed: return True
else: return None
Another possibility that came to my mind is this:
class OneModel(models.Model):
(...)
def viewed(self, user):
profile = user.profile
viewed = self.viewed_episodes.filter(user=profile).exists()
if viewed: return True
else: return None
But I think neither of this are possible. Maybe what I need is a template tag?

Second one is correct.
def viewed(self, user):
return self.viewed_episodes.filter(user=user.profile).exists() or None

Related

DRF Object level permissions check for single field

How do you add permissions to a model so that any user can add a new instance, but only a logged in user can add a particular attribute?
Django models.py:
class Ingredient(models.Model):
name = models.CharField(max_length=100, unique=True)
recipes = models.ManyToManyField(Recipe, related_name='ingredients', blank=True)
DRF views.py:
class IngredientViewSet(viewsets.ModelViewSet):
queryset = Ingredient.objects.all()
serializer_class = IngredientSerializer
permission_classes = (IsUserForRecipeOrBasicAddReadOnly,)
DRF permissions.py:
class IsUserForRecipeOrBasicAddReadOnly(permissions.BasePermission):
"""
Custom permission to only allow logged in users to add an ingredient AND associate its recipe(s).
"""
message = 'You must be logged in to add an Ingredient to a Recipe.'
# using this method so I can access the model obj itself
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
if request.method in permissions.SAFE_METHODS:
return True
# Check if we are creating, and if the recipes are included, and if they are not a user. If so, return False
if request.method == 'POST' and obj.recipes.all().count() > 0 and request.user.is_anonymous:
return False
else:
return True
I see the appropriate calls/prints to the custom permission class, but I can still make a POST request with a list of recipe id's and it does not error with the message.
Notes -
I am getting two POST requests, both with the same information/print statements, and then a third to the GET (once it is added, it shows the newly created instance - which is correct behavior, but I don't know why two POSTs are going through)
I think a better approach would be to use 2 different serializer(one of them hase Recipes as a writable field and the other does not), then override get_serializer_class:
class yourMOdelviewset():
...
...
def get_serializer_class(self):
if self.action == 'create':
if self.request.user.is_authenticated:
return SerializerThatHasRecipesAsAWriteableField
else:
return SerializerThatHasNot
return super().get_serializer_class()
p.s. Drf uses object level permission for retrieving or updating (basically there should be an object already), since in create there is no object yet, drf never checks the object level permission.
The solution proposed by #changak is a good one. Including this as a more direct solution to the question posed. In DRF, has_object_permission is explicitly for an object already in the database, but you can use has_permission. From the docs, this excerpt explains why you don't see has_object_permission being called:
Note: The instance-level has_object_permission method will only be
called if the view-level has_permission checks have already passed.
In has_permission, you still have access to the data, and can add a check. Assuming your IngredientSerializer has a recipes field, you can check with something like this:
class IsUserForRecipeOrBasicAddReadOnly(permissions.BasePermission):
"""
Custom permission to only allow logged in users to add an ingredient AND associate its recipe(s).
"""
message = 'You must be logged in to add an Ingredient to a Recipe.'
def has_permission(self, request, view):
if view.action != 'create':
# Handle everything but create at the object level.
return True
if not request.data.get('recipes'):
return True
return request.user and request.user.is_authenticated()

How to show /account/profile without pass the user like parameter?

I want to show User profile in url /account/profile.
I have a detail class based view,
class UserDetail(generic.DetailView):
model = User
slug_field = 'username'
slug_url_kwarg = 'username'
template_name = 'myuser/user_detail.html'
I have a error:
AttributeError at /accounts/profile/
Generic detail View UserDetail must be called with either an object pk or a slug.
How can I charge the username without pass it like parameter in the url?
(ie: /account/prifle/username), the user is already authenticated.
I see something similar here: http://programtalk.com/vs2/?source=python/12247/horas/apps/profiles/views.py but doesn't work to me.
I tried modify get_queryset, dispatch, and nothing work, I don't know where can modify to get the right result.
Any idea? Thanks
That code is very bizarre and you should not follow it. Overriding get_object is the right idea in your case though; but that method should always actually return an object.
class UserDetail(generic.DetailView):
model = User
def get_object(self, *args, **kwargs):
return self.request.user

Django Generic UpdateView and Multitable Inheritance

Let's say I have the following models:
class Post(model):
...
class BlogPost(Post):
...
class OtherPost(Post):
...
Assume my url schema to edit a post is something like,
/site/post/\d+/edit
In other words, I don't have separate url paths for editing OtherPosts vs. BlogPost.
When using UpdateView, I need to set the model -- but of course, the actual model is a subclass of Post.
class Update(generics.UpdateView):
model = Post
What is the Djangoey/DRY way to handle this?
At the moment, looking over the UpdateView code, it looks like I could leave Update.model undefined, and override get_queryset, which would need to return a query with the right submodel. I would also need to override get_form to return the right form.
I'll post my solution when I get it working, but am looking for possibly better (DRYer) integrations.
It looks like the following method is working, which seems fairly minimal.
class Update(generic.edit.UpdateView):
model = Post
def get_form_class(self):
try:
if self.object.blogpost:
return BlogPostForm
except Post.DoesNotExist:
pass
try:
if self.object.otherpost:
return OtherPostForm
except Post.DoesNotExist:
pass
def get_object(self, queryset=None):
object = super(Update, self).get_object(queryset)
try:
return object.blogpost
except Post.DoesNotExist:
pass
try:
return object.otherpost
except Post.DoesNotExist:
pass
Or, if using a polymorphic mixin like InheritanceManager, then something like this:
class Update(generic.edit.UpdateView):
model = Post
form_class = {
BlogPost: BlogPostForm,
OtherPost: OtherPostForm,
}
def get_form_class(self):
return self.form_class[self.object.__class__]
def get_queryset(self):
return self.model.objects.select_subclasses()

Django REST Framework ModelSerializer get_or_create functionality

When I try to deserialize some data into an object, if I include a field that is unique and give it a value that is already assigned to an object in the database, I get a key constraint error. This makes sense, as it is trying to create an object with a unique value that is already in use.
Is there a way to have a get_or_create type of functionality for a ModelSerializer? I want to be able to give the Serializer some data, and if an object exists that has the given unique field, then just return that object.
In my experience nmgeek's solution won't work in DRF 3+ as serializer.is_valid() correctly honors the model's unique_together constraint. You can work around this by removing the UniqueTogetherValidator and overriding your serializer's create method.
class MyModelSerializer(serializers.ModelSerializer):
def run_validators(self, value):
for validator in self.validators:
if isinstance(validator, validators.UniqueTogetherValidator):
self.validators.remove(validator)
super(MyModelSerializer, self).run_validators(value)
def create(self, validated_data):
instance, _ = models.MyModel.objects.get_or_create(**validated_data)
return instance
class Meta:
model = models.MyModel
The Serializer restore_object method was removed starting with the 3.0 version of REST Framework.
A straightforward way to add get_or_create functionality is as follows:
class MyObjectSerializer(serializers.ModelSerializer):
class Meta:
model = MyObject
fields = (
'unique_field',
'other_field',
)
def get_or_create(self):
defaults = self.validated_data.copy()
identifier = defaults.pop('unique_field')
return MyObject.objects.get_or_create(unique_field=identifier, defaults=defaults)
def post(self, request, format=None):
serializer = MyObjectSerializer(data=request.data)
if serializer.is_valid():
instance, created = serializer.get_or_create()
if not created:
serializer.update(instance, serializer.validated_data)
return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
However, it doesn't seem to me that the resulting code is any more compact or easy to understand than if you query if the instance exists then update or save depending upon the result of the query.
#Groady's answer works, but you have now lost your ability to validate the uniqueness when creating new objects (UniqueValidator has been removed from your list of validators regardless the cicumstance). The whole idea of using a serializer is that you have a comprehensive way to create a new object that validates the integrity of the data you want to use to create the object. Removing validation isn't what you want. You DO want this validation to be present when creating new objects, you'd just like to be able to throw data at your serializer and get the right behavior under the hood (get_or_create), validation and all included.
I'd recommend overwriting your is_valid() method on the serializer instead. With the code below you first check to see if the object exists in your database, if not you proceed with full validation as usual. If it does exist you simply attach this object to your serializer and then proceed with validation as usual as if you'd instantiated the serializer with the associated object and data. Then when you hit serializer.save() you'll simply get back your already created object and you can have the same code pattern at a high level: instantiate your serializer with data, call .is_valid(), then call .save() and get returned your model instance (a la get_or_create). No need to overwrite .create() or .update().
The caveat here is that you will get an unnecessary UPDATE transaction on your database when you hit .save(), but the cost of one extra database call to have a clean developer API with full validation still in place seems worthwhile. It also allows you the extensibility of using custom models.Manager and custom models.QuerySet to uniquely identify your model from a few fields only (whatever the primary identifying fields may be) and then using the rest of the data in initial_data on the Serializer as an update to the object in question, thereby allowing you to grab unique objects from a subset of the data fields and treat the remaining fields as updates to the object (in which case the UPDATE call would not be extra).
Note that calls to super() are in Python3 syntax. If using Python 2 you'd want to use the old style: super(MyModelSerializer, self).is_valid(**kwargs)
from django.core.exceptions import ObjectDoesNotExist, MultipleObjectsReturned
class MyModelSerializer(serializers.ModelSerializer):
def is_valid(self, raise_exception=False):
if hasattr(self, 'initial_data'):
# If we are instantiating with data={something}
try:
# Try to get the object in question
obj = Security.objects.get(**self.initial_data)
except (ObjectDoesNotExist, MultipleObjectsReturned):
# Except not finding the object or the data being ambiguous
# for defining it. Then validate the data as usual
return super().is_valid(raise_exception)
else:
# If the object is found add it to the serializer. Then
# validate the data as usual
self.instance = obj
return super().is_valid(raise_exception)
else:
# If the Serializer was instantiated with just an object, and no
# data={something} proceed as usual
return super().is_valid(raise_exception)
class Meta:
model = models.MyModel
There are a couple of scenarios where a serializer might need to be able to get or create Objects based on data received by a view - where it's not logical for the view to do the lookup / create functionality - I ran into this this week.
Yes it is possible to have get_or_create functionality in a Serializer. There is a hint about this in the documentation here: http://www.django-rest-framework.org/api-guide/serializers#specifying-which-fields-should-be-write-only where:
restore_object method has been written to instantiate new users.
The instance attribute is fixed as None to ensure that this method is not used to update Users.
I think you can go further with this to put full get_or_create into the restore_object - in this instance loading Users from their email address which was posted to a view:
class UserFromEmailSerializer(serializers.ModelSerializer):
class Meta:
model = get_user_model()
fields = [
'email',
]
def restore_object(self, attrs, instance=None):
assert instance is None, 'Cannot update users with UserFromEmailSerializer'
(user_object, created) = get_user_model().objects.get_or_create(
email=attrs.get('email')
)
# You can extend here to work on `user_object` as required - update etc.
return user_object
Now you can use the serializer in a view's post method, for example:
def post(self, request, format=None):
# Serialize "new" member's email
serializer = UserFromEmailSerializer(data=request.DATA)
if not serializer.is_valid():
return Response(serializer.errors,
status=status.HTTP_400_BAD_REQUEST)
# Loaded or created user is now available in the serializer object:
person=serializer.object
# Save / update etc.
A better way of doing this is to use the PUT verb instead, then override the get_object() method in the ModelViewSet. I answered this here: https://stackoverflow.com/a/35024782/3025825.
A simple workaround is to use to_internal_value method:
class MyModelSerializer(serializers.ModelSerializer):
def to_internal_value(self, validated_data):
instance, _ = models.MyModel.objects.get_or_create(**validated_data)
return instance
class Meta:
model = models.MyModel
I know it's a hack, but in case if you need a quick solution
P.S. Of course, editing is not supported
class ExpoDeviceViewSet(viewsets.ModelViewSet):
permission_classes = [IsAuthenticated, ]
serializer_class = ExpoDeviceSerializer
def get_queryset(self):
user = self.request.user
return ExpoDevice.objects.filter(user=user)
def perform_create(self, serializer):
existing_token = self.request.user.expo_devices.filter(
token=serializer.validated_data['token']).first()
if existing_token:
return existing_token
return serializer.save(user=self.request.user)
In case anyone needs to create an object if it does not exist on GET request:
class MyModelViewSet(viewsets.ModelViewSet):
queryset = models.MyModel.objects.all()
serializer_class = serializers.MyModelSerializer
def retrieve(self, request, pk=None):
instance, _ = models.MyModel.objects.get_or_create(pk=pk)
serializer = self.serializer_class(instance)
return response.Response(serializer.data)
Another solution, as I found that UniqueValidator wasn't in the validators for the serializer, but rather in the field's validators.
def is_valid(self, raise_exception=False):
self.fields["my_field_to_fix"].validators = [
v
for v in self.fields["my_field_to_fix"].validators
if not isinstance(v, validators.UniqueValidator)
]
return super().is_valid(raise_exception)

Using different templates with django form wizard

I was looking at the documentation and I am not quite sure how to use different templates for each step...
I looked into the source code and it seems that the template name is hardcoded:
class WizardView(TemplateView):
"""
The WizardView is used to create multi-page forms and handles all the
storage and validation stuff. The wizard is based on Django's generic
class based views.
"""
storage_name = None
form_list = None
initial_dict = None
instance_dict = None
condition_dict = None
template_name = 'formtools/wizard/wizard_form.html'
...........
The docs say something about mixins, but I am not sure how to use them since I just started with django...
Thanks
UPDATE:
I looked further into the source code and realized that there is a method get_template_names.
I tried:
class AddWizard(SessionWizardView):
def get_template_names(self, step):
if step == 0:
return 'business/add1.html'
return 'business/add2.html'
def done(self, form_list, **kwargs):
return render_to_response('business/done.html', {
'form_data': [form.cleaned_data for form in form_list],
})
But got an error:
get_template_names() takes exactly 2 arguments (1 given)
get_template_names doesn't accept arguments. You can't just define a new argument for a function to accept and hope the framework will pass it in! (for your future troubleshooting)
Judging by the WizardView source, it looks like you can access the currently active step via self.steps.current which you could use in your get_template_names view to return a path containing the step.
class AddWizard(SessionWizardView):
def get_template_names(self):
return ['step_{0}_template.html'.format(self.steps.current)]
I'm not sure if current is a string or integer or what - but one look at the view and you should find a useful "can't find template named X" error.