I have two django models related with a OneToOneField. I'll show the relevant fields:
class User(AbstractBaseUser, PermissionsMixin):
email = models.EmailField(unique=True, db_index=True)
And
class PasswordRecovery(models.Model):
user = models.OneToOneField(User, primary_key=True)
token = models.CharField(default=utils.make_token)
password = models.CharField(max_length=128)
Then I have a form for the PasswordRecovery model
class PasswordRecoveryForm(forms.ModelForm):
password_check = forms.CharField(max_length=128)
class Meta:
model = PasswordRecovery
fields = ('user', 'password', 'password_check')
A few details are omitted here. Tell me if you think they are relevant.
The problem is that the user field shows as a 'select' element and I would like it to be an email field. Instead of being able to chose a user from a lista I want it instead to required the email to be typed.
How can I do this?
Thanks in advance
You should override the clean_user method to pass the User objects
class PasswordRecoveryForm(forms.ModelForm):
user = forms.EmailField(required=True)
password_check = forms.CharField(max_length=128)
class Meta:
model = PasswordRecovery
fields = ('user', 'password', 'password_check')
def clean_user(self):
user_email = self.cleaned_data['user']
if User.objects.get(email__iexact=user_email):
return User.objects.get(email__iexact=user_email)
return None
#If you wanna to fill in the email add this
def __init__(self, *args, **kwargs):
super(PasswordRecoveryForm, self).__init__(*args, **kwargs)
if self.instance:
self.fields['user'].initial = self.instance.user.email
Related
I'm working on extending the Basic User model to additional fields. I had created a Profile Model that should have a OneToOneRelation. I'm working with serializers. Now when I try to post a dummy user, I get this error:
**TypeError: User() got an unexpected keyword argument 'street'**
If I send only the user it works well. I know that the 'street' argument is not part of the User, but part of the Profile that should store later.
I tried to solve this with 'request.POST.pop' for every value and parsed to dict but then no data will be transmitted. Additionally, I had no success with Signals.
Does anyone have an idea how I can make this work, as the user and profile will be created at the same time? The user must save first and pass the id its generating to the Profile that is referenced to it.
Models.py:
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=CASCADE, null=True)
street = models.CharField(name="street", max_length=100)
number = models.CharField(name="number", max_length=10)
plz = models.CharField(name="plz", max_length=10)
city = models.CharField(name="city", max_length=100)
phone = models.CharField(name="phone", max_length=20)
locked = models.BooleanField(name="locked", default=False)
Serializer.py:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = '__all__'
class ProfileSerializer(serializers.ModelSerializer):
class Meta:
model = Profile
fields = ['street', 'number', 'plz', 'city', 'phone']
Views.py:
#api_view(['POST'])
def userCreate(request):
userSerializer = UserSerializer(data=request.data)
if userSerializer.is_valid():
user = userSerializer.create(validated_data=request.data)
profileSerializer = ProfileSerializer(instance=user ,data=request.data)
if profileSerializer.is_valid():
profileSerializer.create()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
You can rewrite the serializer to include profile. And then override the create method.
class UserSerializer(serializers.ModelSerializer):
profile = ProfileSerializer()
class Meta:
model = User
fields = '__all__'
def create(self, validated_data):
profile_data = validated_data.pop('profile')
user = User.objects.create(**validated_data)
Profile.objects.create(**profile_data, user=user)
return user
Then your view becomes:
#api_view(['POST'])
def userCreate(request):
userSerializer = UserSerializer(data=request.data)
if userSerializer.is_valid():
user = userSerializer.save()
return Response(status=status.HTTP_200_OK)
return Response(status=status.HTTP_400_BAD_REQUEST)
The request to server should then be modified as:
{
profile: {
... // profile specific attributes like street, number, city
},
... // user specific attibutes
}
PS: To update the instance, you should override the update method of serializer.
I'm using Django 2.x and Django REST Framework.
I have two models like
class Contact(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
user = models.ForeignKey(User, on_delete=models.PROTECT)
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100, blank=True, null=True)
modified = models.DateTimeField(auto_now=True)
created = models.DateTimeField(auto_now_add=True)
class AmountGiven(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
contact = models.ForeignKey(Contact, on_delete=models.PROTECT)
amount = models.FloatField(help_text='Amount given to the contact')
given_date = models.DateField(default=timezone.now)
created = models.DateTimeField(auto_now=True)
the serializer.py the file has serializers defined as
class ContactSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Contact
fields = ('id', 'first_name', 'last_name', 'created', 'modified')
class AmountGivenSerializer(serializers.ModelSerializer):
contact = ContactSerializer()
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'given_date', 'created'
)
views.py
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
def perform_create(self, serializer):
save_data = {}
contact_pk = self.request.data.get('contact', None)
if not contact_pk:
raise ValidationError({'contact': ['Contact is required']})
contact = Contact.objects.filter(
user=self.request.user,
pk=contact_pk
).first()
if not contact:
raise ValidationError({'contact': ['Contact does not exists']})
save_data['contact'] = contact
serializer.save(**save_data)
But when I add a new record to AmountGiven model and passing contact id in contact field
it is giving error as
{"contact":{"non_field_errors":["Invalid data. Expected a dictionary, but got str."]}}
When I remove contact = ContactSerializer() from AmountGivenSerializer, it works fine as expected but then in response as depth is set to 1, the contact data contains only model fields and not other property fields defined.
I'm not a big fan of this request parsing pattern. From what I understand, you want to be able to see all the contact's details when you retrieve an AmountGiven object and at the same time be able to create and update AmountGiven by just providing the contact id.
So you can change your AmountGiven serializer to have 2 fields for the contact model field. Like this:
class AmountGivenSerializer(serializers.ModelSerializer):
contact_detail = ContactSerializer(source='contact', read_only=True)
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'contact_detail', 'amount', 'given_date', 'created'
)
Note that the contact_detail field has a source attribute.
Now the default functionality for create and update should work out of the box (validation and everything).
And when you retrieve an AmountGiven object, you should get all the details for the contact in the contact_detail field.
Update
I missed that you need to check whether the Contact belongs to a user (however, I don't see a user field on your Contact model, maybe you missed posting it). You can simplify that check:
class AmountGivenViewSet(viewsets.ModelViewSet):
serializer_class = AmountGivenSerializer
def perform_create(self, serializer):
contact = serializer.validated_data.get('contact')
if contact.user != self.request.user:
raise ValidationError({'contact': ['Not a valid contact']})
serializer.save()
Override the __init__() method of AmountGivenSerializer as
class AmountGivenSerializer(serializers.ModelSerializer):
def __init__(self, *args, **kwargs):
super(AmountGivenSerializer, self).__init__(*args, **kwargs)
if 'view' in self.context and self.context['view'].action != 'create':
self.fields.update({"contact": ContactSerializer()})
class Meta:
model = AmountGiven
depth = 1
fields = (
'id', 'contact', 'amount', 'given_date', 'created'
)
Description
The issue was the DRF expects a dict like object from contact field since you are defined a nested serializer. So, I removed the nested relationship dynamically with the help of overriding the __init__() method
For those who got here but have relatively simple serializers, this error can also occur when the request data is malformed, in my case JSON encoded twice.
The serializer will decode the JSON, but as it is encoded twice request.data will still be a string. The error therefore makes sense as a "dictionnary" was expected, but we still have a "string".
You can check the output of the following to confirm whether this is the issue you are experiencing:
print(type(request.data)) # Should be <class 'dict'>
My question is somewhat related to this one with some differences. I have a model similar to this one:
class Project(models.Model):
project_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False, unique=True)
created_by_id = models.ForeignKey('auth.User', related_name='project', on_delete=models.SET_NULL, blank=True, null=True)
created_by = models.CharField(max_length=255, default="unknown")
created = models.DateTimeField(auto_now_add=True)
With the following serializer:
class ProjectSerializer(serializers.ModelSerializer):
created_by = serializers.ReadOnlyField(source='created_by_id.username')
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created')
And corresponding view:
class projectsView(mixins.ListModelMixin,
mixins.CreateModelMixin,
generics.GenericAPIView):
queryset = Project.objects.all()
serializer_class = ProjectSerializer
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
def perform_create(self, serializer):
serializer.save(created_by_id=self.request.user)
This code behaves like I want but forces information redundancy and does not leverage the underlying relationnal database. I tried to use the info from the linked question to achieve a "write user id on database but return username on "get"" in a flat json without success:
Removing the "created_by" field in the model. Replacing the serializer with:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Which would NOT 100% give me what I want, i.e. replace the user id with the username in a flat json but return something like: {'project_id': <uuid>, 'created_by': <user json object>, 'created': <data>}. But still I get a {'created_by_id': ['This field is required.']} 400 error.
Question: How can I write a user id to a database object from the request.user information to refer to an actual user id but return a simple username in the GET request on the projectsView endpoint without explicitly storing the username in the Model? Or more generally speaking, how can I serialize database objects (Django models) into customer json response by using default serialization DRF features and default DRF views mixins?
Alternate formulation of the question: How can I store an ID reference to another DB record in my model (that can be accessed without it being supplied by the payload) but deserialize a derived information from that object reference at the serializer level such as one specific field of the referenced object?
I would recommend you to use Two different serializers for Get and POST operations. Change your serializers.py as
class ProjectGetSerializer(serializers.ModelSerializer):
created_by_id = serializers.StringRelatedField()
class Meta:
model = Project
fields = '__all__'
class ProjectCreateSerializer(serializers.ModelSerializer):
created_by_id = serializers.PrimaryKeyRelatedField(queryset=User.objects.all(), default=serializers.CurrentUserDefault())
def create(self, validated_data):
return Project.objects.create(**validated_data, created_by=validated_data['created_by_id'].username)
class Meta:
model = Project
fields = '__all__'
Also, I reccomend ModelViewSet for API class if you are looking for CRUD operations. Hence the view will be like this,
class projectsView(viewsets.ModelViewSet):
queryset = Project.objects.all()
def get_serializer_class(self):
if self.action == 'create':
return ProjectCreateSerializer
return ProjectGetSerializer
So, the payload to create Project is,
{
}
One thing you should remember, while you trying to create Project user must logged-in
UPDATE - 1
serializer.py
class ProjectCreateSerializer(serializers.ModelSerializer):
created_by_id = serializers.StringRelatedField()
class Meta:
model = Project
fields = '__all__'
def create(self, validated_data):
return Project.objects.create(**validated_data, created_by_id=self.context['request'].user)
views.py
class projectsView(viewsets.ModelViewSet):
queryset = Project.objects.all()
serializer_class = ProjectCreateSerializer
The error is in the write_only field options. The required parameter default value is set to True while the intent is to not make it required if we take a look at the model. Here in the view, I use the perform_create as post processing to save on the Model DB representation. Since required default value is True at the creation level, the first .save() to the DB fails. Since this is purely internal logic, the required is not necessary. So simply adding the required=False option on the PrimaryKeyRelatedField does the job:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True, required=False)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Enforcing the required=True at the Model level as well would require to override the .save function of the serializer if I insist on playing with the logic purely at the serializer level for deserialization. There might be a way to get the user ref within the serializer as well to keep the views implementation even more 'default'... This can be done by using the default value from Jerin:
class ProjectSerializer(serializers.ModelSerializer):
created_by = UserSerializer(read_only=True)
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by',
write_only=True,
required=False,
default=serializers.CurrentUserDefault())
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
Now to flaten the json with username only, you need to use a slug field instead of the UserSerializer:
class ProjectSerializer(serializers.ModelSerializer):
created_by = serializers.SlugRelatedField(
queryset=User.objects.all(), slug_field="username")
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), source='created_by', write_only=True, required=False)
class Meta:
model = Project
fields = ('project_id', 'created_by', 'created_by_id', 'created')
And then only the username field value of the User Model will show at the create_by json tag on the get payload.
UPDATE - 1
After some more tweaking here is the final version I came up with:
class ProjectSerializer(serializers.ModelSerializer):
created_by_id = serializers.PrimaryKeyRelatedField(
queryset=User.objects.all(), write_only=True, required=False, default=serializers.CurrentUserDefault())
created_by = serializers.SerializerMethodField('creator')
def creator(self, obj):
return obj.created_by_id.username
class Meta:
model = Project
fields = ('project_id', 'created_by_id', 'created_by', 'created')
I am trying to store use the values from two different models to show as choices in form and store them in new model.
Here is what I have now.
models.py
class Employee(models.Model):
name = models.CharField(max_length=200)
class Product(models.Model):
code = models.CharField(primary_key=True, max_length=5)
class JobQueue(models.Model):
emp_name = models.CharField(max_length=200)
product_code = models.CharField(max_length=5)
forms.py
class JobQueueForm(forms.ModelForm):
emp_choices = Employee._meta.get_field('name').choices
product_code_choices = Product._meta.get_field('code').choices
emp_name = forms.ChoiceField(choices = emp_choices)
product_code =forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=product_code_choices)
def save(self, commit=True):
return super(JobQueueForm, self).save(commit = commit)
class Meta:
model = JobQueue
fields = ('emp_name', 'product_code')
admin.py
class JobQueueAdmin(admin.ModelAdmin):
form = JobQueueForm
fieldsets = (
(None,{
'fields': ('emp_name', 'product_code'),}),
)
def save_model(self, request, obj, form, change):
super(JobQueueAdmin, self).save_model(request, obj, form, change)
admin.site.register(models.Employee, AuthorAdmin)
admin.site.register(models.Product, ProductAdmin)
admin.site.register(models.JobQueue, JobQueueAdmin)
I do have values stored in Employee and Product models, but I dont see them as options in JobQueue model in Admin portal.
Am I missing anything here?
emp_choices = Employee._meta.get_field('name').choices
This line doesn't make sense. It tries to get the choices for the Employee.name field, but you did not specify any choices for the name field in your model.
class Employee(models.Model):
name = models.CharField(max_length=200)
If you want generate a list of choices from all the existing employees in the database, then you can define a method that does this:
def get_names():
return Employee.objects.values_list('name', 'name')
Then use this method in your form:
class JobQueueForm(forms.ModelForm):
emp_name = forms.ChoiceField(choices=get_names)
...
I am trying to create a Model and ModelForm with "name" and "client" fields that have the following cleaning and validation characteristics. I can manage each individual requirement but can't seem get them to work together.
An authenticated user can enter a name for an Item
Item is saved with the name and forced to the client that is associated with the user account.
Name is cleaned via ' '.join(name.strip().split())
Name is validated so that (cleaned_name.lower(),client) is unique
EG: If "FOO BAR" exists in the user's associated client, user would get an error if they enter "foo bar"
It is a fairly simple model:
class Item(BaseModel):
class Meta:
unique_together = (("client", "name"),)
client = models.ForeignKey(Client,related_name='items',null=True,blank=False)
name = models.CharField(max_length=64, null=False, blank=False)
def clean_name(self):
return ' '.join(self.cleaned_data['name'].strip().split())
All item creates/updates are done via Django REST Framework:
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('id','name')
def create(self,validated_data):
item = Item.objects.create(name=validated_data['name'],client=self.context['request'].user.client)
item.save()
return item
I would prefer as much of the logic in the Model as possible (eg, not use SQL to create indexes), but could push some of the validation to the serializer if need be.
Tx.
I ended up with the following. The only caveat is that I have to include a name_slug field to store for sorting purposes.
models.py
class Item(BaseModel):
class Meta:
db_table = 'item'
ordering = ['name_slug']
# relations
client = models.ForeignKey(Client,related_name='items',null=True,blank=False)
# attributes
name = models.CharField(max_length=64, null=False, blank=False)
name_slug = models.CharField(max_length=64, null=False, blank=True)
def clean(self):
self.name = ' '.join(self.name.strip().split())
if Item.objects.filter(client=self.client,name__iexact=self.name).count() > 0:
raise ValidationError({'name': 'Name already exists. Please enter a different name'})
def save(self, *args, **kwargs):
self.name_slug = '-'.join(self.name.split()).lower()
super(Item, self).save(*args, **kwargs)
serializers.py
class ItemSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Item
fields = ('id','name','name_slug')
read_only_fields = ('name_slug',)
def validate(self, attrs):
attrs['client'] = self.context['request'].user.client
instance = Item(**attrs)
instance.clean()
return { k: getattr(instance,k) for k in attrs.keys()}