I've got a simple newsletter app with a subscription model with fields email and city.
In this model I set unique_together('email', 'city') to avoid having subscription duplicates.
class Subscription(models.Model):
email = models.EmailField(_('Email'), max_length=75)
create_date = models.DateField(_("Creation Date"))
city = models.ForeignKey(City)
class Meta:
unique_together = ('email', 'city')
I created a forms.ModelForm from this model:
class SubscriptionForm(forms.ModelForm):
class Meta:
model = Subscription
This is ok when I create a subscription, but when I want to delete a subscription, using the same form, the form does not validate when setting an existing email/subject pair because of the unique_together in the model. Is there any way to bypass this validation rule or should I write a specific form to unsubscribe?
Thank you
You can probably override the unique_together in your SubscriptionForm but this will remove the validation from your form (and will fail when you will save your model).
Or you can create a new UnSubscriptionForm that will override this unique_together setting only for unsubscription.
Related
I am new with Django Rest Framework and wanted to understand what's the accepted practice for writing Serializers that work with nested relationships.
Say, I have a models called Client and Invoice (this is just an illustrative example):
class Client(models.Model)
name = models.CharField(max_length=256)
class Invoice(models.Model)
client = models.ForeignKey(Client)
date = models.DateTimeField()
amount = models.DecimalField(max_digits=10, decimal_places=3)
I want to create a Serializer for Client that supports the following use cases:
Create a Client
When I create an Invoice, refer to the Client using its id.
Let's say I use this implementation:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['id', 'name']
class InvoiceSerializer(serializers.ModelSerializer):
client = ClientSerializer()
class Meta:
model = Invoice
fields = ['id', 'client', 'date', 'amount']
def create(self, data):
client = Client.objects.get(pk=data['client']['id'])
invoice = Invoice(client=client,
date=datetime.fromisoformat(data['date']),
amount=Decimal(data['amount']))
invoice.save()
With this code, if I try to create an Invoice, I need the client object in the POST data to contain name as well. There is no config of the name field (read_only=True, write_only=True, required=False) that allows me to create and read Client as well as not be required when creating the Invoice.
How should this be solved?
Is the accepted practice that the request include the name field anyways?
Can we somehow created nested models like this? /api/Client/<id:client_id>/Invoice
Do we create multiple Serializer classes for each model - one for it's own viewset, and another for use in other models' viewsets?
Thanks!
This is an accepted pratice, but it has its advantages and disadvantages. Actual good practice depends on your actual needs. Here, as you suggested, while creating an Invoice, you also need to send a client name in the request, which should not be necessary. To overcome that need, one possible practive can be as follows:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['id', 'name']
class InvoiceSerializer(serializers.ModelSerializer):
client = serializers.PrimaryKeyRelatedField(queryset=Client.objects.all())
class Meta:
model = Invoice
fields = ['id', 'client', 'date', 'amount']
With this approach, you only include client's id in the serializer. You'll only need to send a client id in the requset with this approach, and don't need to write a custom create method on the serializer. Disadvantage of this approach is; you do not have the client name when listing invoices. so if you need to display client name when displaying an invoice, we'd need to improve this solution a bit:
class InvoiceSerializer(serializers.ModelSerializer):
client = serializers.PrimaryKeyRelatedField(queryset=Client.objects.all())
client_details = ClientSerializer(source='client', read_only=True)
class Meta:
model = Invoice
fields = ['id', 'client', 'client_details', 'date', 'amount']
With this approach, we have added a read-only field, client_details, that keeps the data in client serilaizer. So for write operations we use client field, which is only an id, and to read details about a client, we use client_details field.
Another approach could be defining a separate client serializer to be used as a child serializer in InvoiceSerializer only:
class ClientSerializer(serializers.ModelSerializer):
class Meta:
model = Client
fields = ['id', 'name']
class InvoiceClientSerializer(serializers.ModelSerializer):
name = serializers.CharField(read_only=True)
class Meta:
model = Client
fields = ['id', 'name']
class InvoiceSerializer(serializers.ModelSerializer):
client = InvoiceClientSerializer()
class Meta:
model = Invoice
fields = ['id', 'client', 'date', 'amount']
def create(self, data):
client = Client.objects.get(pk=data['client']['id'])
invoice = Invoice(client=client,
date=datetime.fromisoformat(data['date']),
amount=Decimal(data['amount']))
invoice.save()
In this approach, we have defined a specia client serializer for use in InvoiceSerializer only, that has name field as read only. So while creating / updating an Invoice, you won't need to send client name, but while listing invoices, you get the client name. Advantage of this approach to the one before use, we do not need to use two separate fields for client field for writing and reading details.
For your second question, it is not supported out of the box by DRF, but you can take a look at this package, which provides that functionality and is listed on DRF's own documentation: https://github.com/alanjds/drf-nested-routers
I have created slicing in views but how to do that using rest framework in django.
username = email
username = username.split("#")
real_username = username[0]
I have done this in views, here is my serializers:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = "all"
class DetailSerializers(serializers.ModelSerializer):
class Meta:
model = Data
fields = "all"
You can override to_internal_value method in your serializer class and write your custom logic there.
You can look at it in docs: https://www.django-rest-framework.org/api-guide/serializers/#advanced-serializer-usage
for an app with three objects - User, Event, and Action where users take actions on events creating an object like:
class Action(models.Model):
event = models.ForeignKey('CauseActivity', on_delete=models.CASCADE, related_name='actions')
user = models.ForeignKey('users.User', on_delete=models.CASCADE, related_name='actions')
type = models.CharField(max_length=100, choices=ACTION_TYPES)
date_created = models.DateTimeField(auto_now_add=True)
date_updated = models.DateTimeField(auto_now=True)
I would like our RESTful API to be able to return the actions for one specific user, either within the user request (which requires no extra API calls) or with one additional request (get actions by user)
a simple User serializer with 'actions' as a field will return only the obj ids (response to GET ex: "actions":[108,109])
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = (
..., 'actions', )
I am aware of nesting to serialize custom fields like username = serializers.ReadOnlyField(source='user.name') but is there a way to use serializers.ListSerializer or .ModelSerializer to serialize the full set of Action objects' fields within my UserSerializer? I read through https://www.django-rest-framework.org/api-guide/serializers/ and best approach isn't really clear to me. thanks
create a serializer for your Action model (let's call it ActionSerializer) then make your code like
class UserSerializer(serializers.ModelSerializer):
actions = ActionSerializer(many=True)
class Meta:
model = User
fields = ('actions',...)
I have 2 different models which contian email field:
class Model1(models.Model):
email = models.EmailFields(unique=True)
"""
other fileds
"""
class Model2(models.Model):
email = models.EmailFields(unique=True)
"""
other fileds
"""
These models don't have to contain the same emails. How can I do that?
Use clean method in each ModelForm for each models:
class Model1Form(forms.ModelForm):
class Meta:
model = Model1
fields = ['email', ...]
def clean(self):
cleaned_data = super().clean()
email = self.cleaned_data.get('email')
if Model2.objects.filer(email=email).exists():
self.add_error('email', 'Email have to be unique')
And as in other ModelForms?
You could use multi-table inheritance. Then all the emails will be stored in a single table, and the unique constraint will be handled at database label.
BaseModel(models.Model):
email = models.EmailFields(unique=True)
class Model1(models.Model):
# other fields
class Model2(models.Model):
# other fields
If you do not want to use multi-table inheritance, you will have to manually check the other table as you are doing. To avoid repetition, maybe you can write a base model form class, and subclass it.
suppose this model:
class Tweek(models.Model):
content = models.CharField(max_length=140)
date = models.DateTimeField(auto_now_add=True)
author = models.ForeignKey(User, related_name='author')
class Meta:
ordering = ['-date']
def __unicode__(self):
return self.content
Everthing works fine, now i try to bind a rest api uppon. I've installed the django rest framework and I can retrieve tweeks but I cannot create new ones.
I have this serializer for the Tweek model:
class TweekSerializer(serializers.ModelSerializer):
author = UserSerializer()
class Meta:
model = Tweek
fields = ('content', 'date', 'author')
def create(self, validated_data):
author_data = validated_data.pop('author')
author = User.objects.get(username=author_data)
return Tweek.objects.create(author=author, **validated_data)
and the user serializer somply looks like:
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name')
but the post returns
{
"author": {
"username": [
"This field must be unique."
]
}
}
I've followed the doc, but i'm lost with this case :(
Any idea ?
The issue here is that any field with unique=True set, like the username field on the Django User model, will automatically have the UniqueValidator added to the serializer field. This validator is what is triggering the message you are seeing, and you can remove the validation by setting validators to [] (an empty list) when initializing the field.
The other issue that you are going to run into is that you are trying to create an object with a foreign key, but you are returning the full serialized object in your response. This issue is easier fixed by using a second field for setting the id, that is write-only, and using the original serializer field for the nested representation and making that read-only.
You can find more information in the following Stack Overflow question: DRF: Simple foreign key assignment with nested serializers?