I have 2 models that are OneToOne related and model that is FK to 2nd model
models.py
class Legal(TimeStampedModel):
name = models.CharField('Name', max_length=255, blank=True)
class LegalCard(TimeStampedModel):
legal = models.OneToOneField('Legal', related_name='legal_card', on_delete=models.CASCADE)
branch = models.ForeignKey('Branch', related_name='branch', null=True)
post_address = models.CharField('Post address', max_length=255, blank=True)
class Branch(TimeStampedModel):
name = models.CharField('Name',max_length=511)
code = models.CharField('Code', max_length=6)
Using DRF I made them to behave as single model so I can create or update both:
serializer.py
class LegalSerializer(serializers.ModelSerializer):
branch = serializers.IntegerField(source='legal_card.branch', allow_null=True, required=False)
post_address = serializers.CharField(source='legal_card.post_address', allow_blank=True, required=False)
class Meta:
model = Legal
fields = ('id',
'name',
'branch',
'post_address',
)
depth = 2
def create(self, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
legal = super(LegalSerializer, self).create(validated_data)
self.update_or_create_legal_card(legal, legal_card_data)
return legal
def update(self, instance, validated_data):
legal_card_data = validated_data.pop('legal_card', None)
self.update_or_create_legal_card(instance, legal_card_data)
return super(LegalSerializer, self).update(instance, validated_data)
def update_or_create_legal_card(self, legal, legal_card_data):
LegalCard.objects.update_or_create(legal=legal, defaults=legal_card_data)
views.py
class LegalDetailView(generics.RetrieveUpdateDestroyAPIView):
queryset = Legal.objects.all()
serializer_class = LegalSerializer
I'm trying to save this by sending FK as integer (I just want to post id of the branch), but I receive error
ValueError: Cannot assign "2": "LegalCard.branch" must be a "Branch" instance.
Is there any way to pass over only ID of the branch?
Thank you
In Django, if you only need the FK value, you can use the FK value that is already on the object you've got rather than getting the related object.
Assume you have a Legal and Branch object with id's as 1. Then you can save a LegalCard object by:
LegalCard(legal_id=1,branch_id=1,post_address="Istanbul Street No:1")
Just use legal_card.branch_id instead of legal_card.branch to get just an id, not a related object.
And depth = 1
Related
I'm trying to add the CountryField to a serializer for the Register process (using dj-rest-auth) and can't find the correct way to implement it.
All the answers I found just say to use what the documentation says, but that doesn't help for me, maybe Im just not doing it right.
This is what the documentation of django-countries says:
from django_countries.serializers import CountryFieldMixin
class CountrySerializer(CountryFieldMixin, serializers.ModelSerializer):
class Meta:
model = models.Person
fields = ('name', 'email', 'country')
I need to add the field here:
class CustomRegisterSerializer(RegisterSerializer, CountryFieldMixin):
birth_date = serializers.DateField()
country = CountryField()
gender = serializers.ChoiceField(choices=GENDER)
# class Meta:
# model = User
# fields = ('country')
# Define transaction.atomic to rollback the save operation in case of error
#transaction.atomic
def save(self, request):
user = super().save(request)
user.birth_date = self.data.get('birth_date')
user.country = self.data.get('country')
user.gender = self.data.get('gender')
user.save()
return user
User Model
class User(AbstractUser):
"""
Default custom user model
"""
name = models.CharField(max_length=30)
birth_date = models.DateField(null=True, blank=True)
country = CountryField(null=True, blank=True, blank_label='Select country')
gender = models.CharField(choices=GENDER, max_length=6, null=True, blank=True)
...
I tried different things besides this and nothing worked.
For the serializer, you import the CountryField of the django_countries.serializer_fields module, so:
from django_countries.serializer_fields import CountryField
class CustomRegisterSerializer(RegisterSerializer):
# …
country = CountryField()
# …
If you instead want to work with the Mixin (which will use such CountryField serializer field), you should specify the CountryFieldMixin before the RegisterSerializer, otherwise it will not override the .build_standard_field(…) method.
You thus inherit with:
class CustomRegisterSerializer(CountryFieldMixin, RegisterSerializer):
# …
In that case you should not specify the country serializer field manually, since that will render the mixin ineffective.
This is my model
class Person(models.Model):
name = models.CharField(max_length=100, blank=True, default='')
is_ignore_validations = models.BooleanField(default=False)
and this is my form
class PersonForm(forms.ModelForm):
class Meta:
model = Person
fields = ['name',
'is_ignore_validations',
]
is_ignore_validations = forms.BooleanField(widget=forms.HiddenInput(), required=False)
The usage for is_ignore_validations is in the clean_name() function
def clean_name():
original_name = self.data.get('name')
if not self.initial.get('is_ignore_validations'):
pattern = re.compile('^[a-zA-Z]+$')
if not pattern.match(original_name):
raise ValidationError('name must consist only of letters')
return original_name
I initiate my form with request.POST, which doesn't have is_ignore_validations.
I want it to stay how I've set it manually in the DB, but when I use form.save() it always changes to False.
So firstly, how do I keep is_ignore_validations data in DB always the same?
Secondly, is the usage in clean_name of the variable from initial best practice - self.initial.get('is_ignore_validations')?
These are simplified versions of my models (the user model is just an id and name)
class Convo(models.Model):
owner = models.ForeignKey(User, on_delete=models.CASCADE, related_name='convo_owner')
users = models.ManyToManyField(User, through='Convo_user')
class Convo_user (models.Model):
user = models.ForeignKey(UserProfile, on_delete=models.CASCADE)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
class Comments(models.Model):
name = models.CharField(max_length=255)
content = models.TextField(max_length=1024)
convo = models.ForeignKey(Convo, on_delete=models.CASCADE)
This is my view
class ConvoViewSet(viewsets.ModelViewSet):
serializer_class = serializers.ConvoSerializer
def get_queryset(self):
return None
def list(self, request):
curr_user = request.user.id
# Collecting the list of conversations
conversations = models.Conversation.object.filter(ConvoUser__user_id=request.user.id)
#Getting list of conversation id's
conv_ids = list(conversations.values_list('id', flat=True).order_by('id'))
#Getting list of relevant comments
comments = models.Comments.objects.filter(conversation_id__in=conv_ids)
return Response(self.get_serializer(conversations, many=True).data)
And my current serializer
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
# access = AccessSerializer(many=True)
# model = models.Comments
# fields = ('id', 'name', 'content', 'convo_id')
class Meta:
model = models.Convo
fields = ('id', 'owner_id')
The current response I get is of the form
[
{
"id": 1,
"owner_id": 32
}, ...
]
But I would like to add a comments field that shows all the properties of comments into the response, so basically everything in the second queryset (called comments) and I'm not sure how to go about this at all. (I retrieve the comments in the way I do because I'm trying to minimize the calls to the database). Would I need to create a new view for comments, make its own serializer and then somehow combine them into the serializer for the convo?
The way you've set up your models, you can access the comments of each Convo through Django's ORM by using convo_object.comments_set.all(), so you could set up your ConvoSerializer to access that instance's comments, like this:
class ConvoSerializer(serializers.ModelSerializer):
"""A serializer for messaging objects"""
comments_set = CommentSerializer(many=True)
class Meta:
model = models.Convo
fields = ('id', 'owner_id', 'comments_set')
and then you define your CommentSerializer like:
class CommentSerializer(serializers.ModelSerializer):
class Meta:
model = models.Comments
fields = ('id', 'name', 'content')
No data appears because my serializers are using the default database, not sure why but a step forward
EDIT:
Django: Database used for prefetch_related is not the same that the parent query Provided me the correct answer, I was able to choose the database with this method because for some reason inner queries use the default DB
I'm creating this simple shopping API in Django REST.
Internally I'm using IDs for foreign key constraints, while guuids are brought to the outside world.
For the checkout procedure, the user provides a list of article IDs he is willing to purchase. The object in the POST data thus looks as follows:
{
assets: [
{
'product': 'd9d5044d-2284-4d15-aa76-2eee3675035b',
'amount': 4
},
....
]
}
I'm using the following ticket/asset models:
# Ticket
class Ticket(models.Model):
uuid = models.UUIDField(default=uuid.uuid4, editable=False, unique=True)
owner = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='tickets', on_delete=models.CASCADE)
# Assets
class Asset(models.Model):
ticket = models.ForeignKey(Ticket, related_name='assets', on_delete=models.CASCADE)
stock_item = models.ForeignKey(Stock, related_name='stock_item', on_delete=models.SET_NULL, null=True)
amount = models.IntegerField(validators=[MinValueValidator(0)])
And the serializers look as follows:
# Asset serializer
class AssetSerializer(serializers.ModelSerializer):
class Meta:
model = Asset
fields = ('stock_item', 'amount')
# Ticket serializer
class TicketSerializer(WritableNestedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
assets = AssetSerializer(many=True)
class Meta:
model = Ticket
fields = ('uuid', 'owner', 'assets', )
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
When posting an object of the type specified above, the following error is presented:
{"assets":[{"stock_item": ["Invalid type. Expected PK, received string"]}]}
Which I can't seem to solve, how do I instruct the serializer to use the uuid as the lookup value? I solved a similar problem on view-level earlier by using the lookup_field member, but that doesn't seem to solve it. Any suggestions?
Enter code here
If I have understood you correctly, a SlugRelatedField should be able to find the correct related object.
class AssetSerializer(serializers.ModelSerializer):
ticket = serializers.SlugRelatedField(
read_only=True,
slug_field='uuid',
queryset=Ticket.objects.all() # Might be redundant with read_only=True
)
class Meta:
model = Asset
fields = ('ticket', 'stock_item', 'amount')
Elaborating on #BjornW's comment:
class UUIDRelatedField(serializers.SlugRelatedField):
slug_field = 'uuid'
def __init__(self, **kwargs):
super().__init__(slug_field=self.slug_field, **kwargs)
def to_representation(self, obj):
return getattr(obj, self.slug_field).hex
I've the following models:
class ModelX(models.Model):
STATUS_CHOICES = (
(0, 'ABC'),
(1, 'DEF'),
)
status = models.IntegerField(choices=STATUS_CHOICES)
user = models.ForeignKey(Users)
class Users(models.Model):
phone_number = models.Charfield()
and the serializer for ModelX is :
class ModelXSerializer(serializers.ModelSerializer):
phone_number = serializers.PrimaryKeyRelatedField(
source='user', queryset=Users.objects.get(phone_number=phone_number))
class Meta:
model = ModelX
fields = ('phone_number',)
In the request for creating the ModelX record, I get phone_number instead of the user_id. Now, I've to fire a filter query in order get the user instance. How do I do that, ie Users.objects.get(phone_number=phone_number)
Also, when creating a record, the status field will always be 0. The client wont post the status parameter in the body. Its the internal logic. Is there a way in serializers that can set the status field to 0 automatically. Please dont recommend to set this field as default=0 in models. There's some logic behind this. This is just a shorter version of the problem statement.
you can try such version of your serializer, with custom create method:
class ModelXSerializer(serializers.ModelSerializer):
phone_number = serializers.CharField(source='user.phone_number')
class Meta:
model = ModelX
fields = ('phone_number',)
def create(self, validated_data):
phone_number = validated_data['phone_number']
user = Users.objects.get(phone_number=phone_number)
instance = ModelX.objects.create(status=0, user=user)
return instance
details about the source argument.
You can add it in validate function:
class ModelXSerializer(serializers.ModelSerializer):
def validate(self, attrs):
attrs = super(ModelXSerializer, self).validate(attrs)
attrs['status'] = 0
return attrs