UniqueValidator with source field - django

I have model UserProfile:
class UserProfile(models.Model):
user = models.OneToOneField(User)
and serializer:
email = serializers.CharField(source='user.email', required=False,
validators=[UniqueValidator(queryset=User.objects.all())])
Serialization works fine, but deserialization doesn't — it tries to file 'user.email' field in User model, and, of course, fails.
If I change User to UserProfile in the queryset, it fails with another error:
invalid literal for int() with base 10: 'admin#localhost'
Is it possible to set different sources for serialization and deserialization?

By default, the UniqueValidator expects that the source (or field name, if no source is given) can be used in the queryset that is provided to filter out the existing object. So by using a source that spans a relation (and has a dot), it will try to use that name when filtering the queryset which fails, as you've noticed.
You can fix this by subclassing UniqueValidator to override the filter_queryset method to filter it differently.
class CustomUniqueValidator(UniqueValidator):
def filter_queryset(self, value, queryset):
"""
Filter the queryset to all instances matching the given attribute.
"""
filter_kwargs = {"email": value}
return queryset.filter(**filter_kwargs)
This hard codes email as the filter, one possible option for not hard coding it would be to split self.field_name and get the last part of the dotted source.

Related

Django: How to Properly Use ManyToManyField with Factory Boy Factories & Serializers?

The Problem
I am using a model class Event that contains an optional ManyToManyField to another model class, User (different events can have different users), with a factory class EventFactory (using the Factory Boy library) with a serializer EventSerializer. I believe I have followed the docs for factory-making and serializing, but am receiving the error:
ValueError: "< Event: Test Event >" needs to have a value for field "id"
before this many-to-many relationship can be used.
I know that both model instances must be created in a ManyToMany before linking them, but I do not see where the adding is even happening!
The Question
Can someone clarify how to properly use a ManyToManyField using models, factory boy, and serializers in a way I am not already doing?
The Set-Up
Here is my code:
models.py
#python_2_unicode_compatible
class Event(CommonInfoModel):
users = models.ManyToManyField(User, blank=True, related_name='events')
# other basic fields...
factories.py
class EventFactory(factory.django.DjangoModelFactory):
class Meta:
model = models.Event
#factory.post_generation
def users(self, create, extracted, **kwargs):
if not create:
# Simple build, do nothing.
return
if extracted:
# A list of users were passed in, use them
# NOTE: This does not seem to be the problem. Setting a breakpoint
# here, this part never even fires
for users in extracted:
self.users.add(users)
serializers.py
class EventSerializer(BaseModelSerializer):
serialization_title = "Event"
# UserSerializer is a very basic serializer, with no nested
# serializers
users = UserSerializer(required=False, many=True)
class Meta:
model = Event
exclude = ('id',)
test.py
class EventTest(APITestCase):
#classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user(email='test#gmail.com',
password='password')
def test_post_create_event(self):
factory = factories.EventFactory.build()
serializer = serializers.EventSerializer(factory)
# IMPORTANT: Calling 'serializer.data' is the exact place the error occurs!
# This error does not occur when I remove the ManyToManyField
res = self.post_api_call('/event/', serializer.data)
Version Info
Django 1.11
Python 2.7.10
Thank you for any help you can give!
Regarding the error:
It seems like the missing id is due to the use of .build() instead of .create() (or just EventFactory()). The former does not save the model, and therefore it does not get an id value, whereas the latter does (refer to factory docs and model docs).
I suspect the serializer still expects the object to have an id, even though the many-to-many relationship is optional, because it cannot enforce a potential relationship without an id.
However, there might be a simpler solution to the actual task. The above method is a way of generating the POST data passed to post_api_call(). If this data was instead created manually, then both the factory and serializer become unnecessary. The explicit data method might even be better from a test-perspective, because you can now see the exact data which has to produce an expected outcome. Whereas with the factory and serializer method it is much more implicit in what is actually used in the test.

Django - filter related objects within model. 'ReverseManyRelatedObjectsDescriptor' object has no attribute 'filter'

This is my model:
class Delivery(models.Model):
name = models.CharField(_(u"Name"), max_length=50)
permissions = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
I'm creating a method to return True or False t check if a certain user can view a certain row:
#classmethod
def can_view(self, user):
permission = self.permissions.filter(permissions = user);
return permission is not None;
And when trying to use this method I get this error: 'ReverseManyRelatedObjectsDescriptor' object has no attribute 'filter'
As you can see I don't know how I can check if the user has permission: permission = self.permissions.filter(permissions = user);. How can I get the related within the model?
Thanks for any help!
You've declared this to be a classmethod, instead of a normal instance method. So the first parameter, which you've named self, is actually the class, not an instance of it; and the class itself doesn't have any related objects.
There's no reason for this to be a classmethod; remove that decorator.
Firstly you should fix your can_view method so it is not a class method, remove #classmethod
The line self.permissions.filter(permissions = user) probably won't work if you're trying to filter on the User. The filter() method is expecting a list of keyword arguments, with the models field name as the key and the value you're filtering on as the, well, value. Here you are filtering on your User model, so you probably want to filter on a field like id (or pk), email, username, or some other unique field. Give the
docs for .filter(**kwargs) a read.
For your query, I'd recommend using return self.permissions.filter(pk=user.pk).exists(). I'm filtering on pk because this is simple and guaranteed to work.
i.e.
def can_view(self, user):
return self.permissions.filter(pk=user.pk).exists()
You have a few issues here:
As others mentioned, self and #classmethod don't go together. self refers to the instance, so its for instance methods.
Your ORM query is also not correct as permissions is a ManyToMany field, and you need to access it accordingly.
Try this method:
def can_view(self, user):
return self.permissions.filter(pk=user.pk).exists()
However, you really don't need this method as all User objects will have a delivery_set model (see the docs), so you can simply do:
def check_if_delivery_belongs_to_user(request, delivery_pk=None):
delivery = get_object_or_404(Delivery, pk=delivery_pk)
if request.user.delivery_set.filter(pk=delivery.pk).exists():
print('You can access this')
else:
print('Nope, you cannot')

How to set a model field based on the current user in a Django CreateView [duplicate]

I have a model named Domain which looks like this:
class Domain(models.Model):
"""
Model for storing the company domains
"""
user = models.ForeignKey(
User
)
host = models.CharField(
null=False, verbose_name="Host", max_length=128, unique=True
)
I'd like to use Django's generic views for doing CRUD operations on this. There is one field in this model that needs user input but the foreign key field doesn't need any user input. How can I exclude that field from the form that my generic view generates but assign it the value of the current authenticated user.
Thanks.
Have a look at Russel's answer to a similar question on the django-users group earlier this week.
Quoting the answer*:
Forms and Views solve different problems.
The View is solving the problem of "how do I handle this request and
convert it into a response?". The Form is solving the problem of "How
do I convert the POST data in this request into a model object (or a
change to a model object)?".
Very roughly, a view is doing the following:
View gets a request
View works out whether this is a GET or a POST
If its a POST, View asks the Form to turn the Post into a model change
Form returns success or failure
View responds to the success or failure of the Form.
View returns a response.
The functionality of the Form is a complete subset of the
functionality of the View -- and for this reason, it's a completely
interchangable internal component.
Now, in simple situations, it's possible for a View to guess all the
defaults for the form -- all it needs to know is that you're dealing
with a Foo model, and it can construct a default Foo ModelForm.
However, if you have more sophisticated form requirements, you're
going to need a customized Form.
We could have implemented this by exposing all the options of
ModelForm on the View class; but in order to keep everything clean, we
kept the ModelForm isolated, and provided the View with a way to
specify which Form class it's going to use.
So - to cover your use case of excluding fields, you define a
ModelForm that excludes the fields, then let the CreateView know the
form you want to use:
class CampaignForm(forms.ModelForm):
class Meta:
model = Campaign
exclude = ('user', 'name', 'content_inlined')
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
I'm guessing when you say "fix a values for a field", you mean setting
the values of user, name and content_inlined before you save the new
Campaign instance; to do this, you need to inject some extra code into
the form processing logic of the form:
class CreateCampaignView(CreateView):
form_class = CampaignForm
template_name = "forms/create.html"
def form_valid(self, form):
form.instance.user = ... (something meaningful.. e.g., self.request.user)
return super(CreateCampaignView, self).form_valid(form)
This overrides the default behavior when the form is valid, and sets
the extra values. The super() implementation of form_valid() will then
save the instance.
For the record, this could also be done by overriding the save()
method on the ModelForm -- however, if you do that, you lose the
request object, which you will need if you're trying to set the
instance values to something that is request-sensitive.
*the original answer set self.object.user instead of form.instance.user. This gives an AttributeError so I have changed it above.

Django CreateView and validation

I'm trying to implement generic views in my Django 1.8 app, so that Django can take care of the validation/redirection loop for me.
I've created a model:
class Customer(models.Model):
custid = models.CharField(max_length=4, verbose_name='CID (4 alphanumeric uppercase)', validators=[validators.CIDValidator])
customer_shortcode = models.CharField(max_length=7, verbose_name='Customer Code (7 chars, uppercase, no spaces)', validators=[validators.ShortnameValidator])
description = models.CharField(max_length=30, blank=True)
and defined a validator for each of my two validated fields:
class CIDValidator(RegexValidator):
regex = r'^[A-Z0-9]{4}$'
message = 'CID is a 4-character uppercase alphanumeric value'
class ShortnameValidator(RegexValidator):
regex = r'^[A-Z0-9_]{1,7}$'
message = 'Shortname should be uppercase, no spaces, alphanumeric'
(At this point, I expected that the admin interface would use the validators when I added a Customer, but it doesn't)
For the actual app, I've created a ModelForm for the Customer class:
class CustomerForm(ModelForm):
class Meta:
model = Customer
fields = ['custid', 'customer_shortcode', 'description']
and a View class inherited from CreateView:
class CustomerCreateView(CreateView):
model = Customer
form_class = CustomerForm
def get_success_url(self):
return reverse('customer_list')
And I still don't get validation errors when I enter invalid data in the generated form.
As far as I can follow from the docs, I should only need to override clean() or clean_xxx() on the ModelForm for additional validation, not for this, but it's really unclear. I'd like to keep the knowledge about what constitutes a valid value in as few places as possible - which the validator on the ModelField would do.
What is missing here? I suspect I'm getting confused between model validation and form validation...
TL;DR: when specifying this kind of validators in model field definitions, you should pass instances rather than classes (validators.CIDValidator() instead of validators.CIDValidator).
Longer explanation
Django validators need to be callables. Trying to call the class you are passing now will go through python's creation sequence for an instance, calling __new__ and __init__, and it would return an instance of that class - but it won't do anything in terms of validating the field value.
The Django validators you are subclassing also have a __call__ method, that is run when you try to call an instance of that class, and it takes care of validating and raising ValidationErrors

Django REST Serializer and unique_together and from_native

I am having trouble using the SlugRelatedField to deserialize a field that is part of unique_together set.
To given an example, I have a Blog model which has a Title and an Author.
The tuple of these uniquely identify a Blog (as in the Title is unique to a given Author, but not unique site wide). I want to look up the Blog from a message containing just these values.
In order to deserialize a message (i.e. from_native), the SlugRelatedField calls: self.queryset.get(**{self.slug_field: data}) which will fail on the Title slug because it is not globally unique. This can be solved by providing a more limited queryset (ie one which contains just Blogs but that user), but I am not sure how/where is the best place to set this queryset in the Field (because I do not know the Author until I get to deserializing that field).
One idea would be to do my own deserialization of the Author in get_fields where I could then filter the queryset for Blog. This code is pretty ugly, likely results in deserialization of Author twice, and has issues when the Serializer is used for the list view (as opposed to the detail view).
You need to set the QuerySet for your field at runtime.
You can do this in the serialiser, something like:
class MyObjectSerializer(serializers.HyperlinkedModelSerializer):
def get_fields(self, *args, **kwargs):
fields = super(MyObjectSerializer, self).get_fields(*args, **kwargs)
fields['slug'].queryset = MyObject.objects.filter(self.context['view'].request.user)
return fields
Or in the view as:
def get_serializer(self):
serializer = super(MyObjectView, self).get_serializer()
serializer.fields['slug'].queryset = MyObject.objects.filter(self.request.user)
It's the same thing — you're setting the QuerySet on the slug field once you have a reference to the current user — it just depends on which is best for you.
I hope that helps.