Django model TextField display zip code format - python

In my models.py, I have a zip code field that are in TextField format:
class Merged(models.Model):
zip_code = models.TextField(db_column='Zip Code', blank=True, null=True)
However, there exists some invalid zip codes like
sagaponack 11962
lindenhurst 11757
I just want to display the 5-digit zip code without any other texts.
What I have tried and failed
In serializers.py, I tried to apply a RegexField to the zip_code column:
class MergedSerializer(serializers.HyperlinkedModelSerializer):
zip_code = serializers.RegexField(regex=r'^\d{5}')
class Meta:
model = Merged
fields = '__all__'
It did not work.
How do I solve this in a way that does not only change how it displays, but also ensures that sorting works properly (i.e. ignore the text part, only sort based on zip codes)

Model.clean() is a method of Model class where can provide a custom validation for your model you can see all the methods and properties of Model clas in this link [https://docs.djangoproject.com/en/3.2/ref/models/instances/][1]
class Article(models.Model):
def clean(self):
/*** your validations **/
if you wanted to separate the validation for models you can go after through ModelForm class which is much better than Model class
class UserFields(forms.ModelForm):
def clean_contact_number(self):
contact_number = self.cleaned_data.get('contact_number')
if ' ' in contact_number:
raise forms.ValidationError('Should not have blank spaces included')
if not str(contact_number).isdigit():
raise forms.ValidationError('Should not have letters or special characters included')
pass
return contact_number
pass
There so may ways to do the custom validation in the Django, even serializers class has own method for validation
class BoxSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Box
fields = ('id', 'name', 'url', 'title')
def validate(self, data):
if self.instance: # 'instance' will be set in case of `PUT` request i.e update
object_id = self.instance.id # get the 'id' for the instance
# write your validation logic based on the object id here
return data
def validate_title(self, value):
"""
Check that the blog post is about Django.
"""
if 'django' not in value.lower():
raise serializers.ValidationError("Blog post is not about Django")
return value

Related

Django form not populating with POST data

SOLUTION AT THE BOTTOM
Problem: Django form populating with list of objects rather than values
Summary: I have 2 models Entities and Breaks. Breaks has a FK relationship to the entity_id (not the PK) on the Entities model.
I want to generate an empty form for all the fields of Breaks. Generating a basic form populates all the empty fields, but for the FK it generates a dropdown list of all objects of the Entities table. This is not helpful so I have excluded this in the ModelForm below and tried to replace with a list of all the entity_ids of the Entities table. This form renders as expected.
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
exclude = ('entity',)
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all().values_list('entity_id', flat=True))
The below FormView is the cbv called by the URL. As the below stands if I populate the form, and for the FK column entity_id choose one of the values, the form will not submit. By that field on the form template the following message appears Select a valid choice. That choice is not one of the available choices.
class ContactFormView(FormView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
My initial thoughts were either that the datatype of this field (string/integer) was wrong or that Django needed the PK of the row in the Entities table (for whatever reason).
So I added a post function to the FormView and could see that the request.body was populating correctly. However I can't work out how to populate this into the ModelForm and save to the database, or overcome the issue mentioned above.
Addendum:
Models added below:
class Entity(models.Model):
pk_securities = models.AutoField(primary_key=True)
entity_id = models.CharField(unique=True)
entity_description = models.CharField(blank=True, null=True)
class Meta:
managed = False
db_table = 'entities'
class Breaks(models.Model):
pk_break = models.AutoField(primary_key=True)
date = models.DateField(blank=True, null=True)
entity = models.ForeignKey(Entity, on_delete= models.CASCADE, to_field='entity_id')
commentary = models.CharField(blank=True, null=True)
active = models.BooleanField()
def get_absolute_url(self):
return reverse(
"item-update", args=[str(self.pk_break)]
)
def __str__(self):
return f"{self.pk_break}"
class Meta:
managed = False
db_table = 'breaks'
SOLUTION
Firstly I got this working by adding the following to the Entity Model class. However I didn't like this as it would have consequences elsewhere.
def __str__(self):
return f"{self.entity_id}"
I found this SO thread on the topic. The accepted answer is fantastic and the comments to it are helpful.
The solution is to subclass ModelChoiceField and override the label_from_instance
class EntityChoiceField(ModelChoiceField):
def label_from_instance(self, obj):
return obj.entity_id
I think your problem is two fold, first is not rendering the dropdown correctly and second is form is not saving. For first problem, you do not need to do any changes in ModelChoiceField queryset, instead, add to_field_name:
class BreakForm(ModelForm):
class Meta:
model = Breaks
#fields = '__all__'
def __init__(self, *args, **kwargs):
super(BreakForm, self).__init__(*args, **kwargs)
self.fields['entity_id'] = ModelChoiceField(queryset=Entities.objects.all(), to_field_name='entity_id')
Secondly, if you want to save the form, instead of FormView, use CreateView:
class ContactFormView(CreateView):
template_name = "breaks/test/breaks_form.html"
form_class = BreakForm
model = Breaks
In Django, the request object passed as parameter to your view has an attribute called "method" where the type of the request is set, and all data passed via POST can be accessed via the request. POST dictionary. The view will display the result of the login form posted through the loggedin. html.

Catch-all field for unserialisable data of serializer

I have a route where meta-data can be POSTed. If known fields are POSTed, I would like to store them in a structured manner in my DB, only storing unknown fields or fields that fail validation in a JSONField.
Let's assume my model to be:
# models.py
from django.db import models
class MetaData(models.Model):
shipping_address_zip_code = models.CharField(max_length=5, blank=True, null=True)
...
unparseable_info = models.JSONField(blank=True, null=True)
I would like to use the built-in serialisation logic to validate whether a zip_code is valid (5 letters or less). If it is, I would proceed normally and store it in the shipping_address_zip_code field. If it fails validation however, I would like to store it as a key-value-pair in the unparseable_info field and still return a success message to the client calling the route.
I have many more fields and am looking for a generic solution, but only including one field here probably helps in illustrating my problem.
As you are looking for a generic solution, there are a few points that you should consider:
Make sure not to place any model-level validations in your model as you want it to get saved irrespective of the validation status.
Only validate on the serializer-level with custom validation methods.
Make unparseable_info field read-only as it is something we don't want the user to send but receive.
Make use of the errors dictionary provided by the serializer as it gets populated with field-specific errors when we call is_valid.
This is how it might translate into code, inside models.py:
class MetaData(models.Model):
shipping_address_zip_code = models.CharField(blank=True, null=True)
...
unparseable_info = models.JSONField(blank=True, null=True)
then inside serializers.py:
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
read_only_fields = ('unparseable_info', )
fields = '__all__'
# Write validators for all of your fields.
finally inside your views.py method, something like this (you can do this inside serializer's save method as well):
meta_data = MetaDataSerializer(data=request.data)
if not meta_data.is_valid():
meta_data.unparseable_info = meta_data.errors
meta_data.save()
# Return meta_data.data in JSONResponse.
You can use Django serializer that store fields that fail validation in JSONField.
Here is an example that worked for me:
from rest_framework import serializers
class MetaDataSerializer(serializers.ModelSerializer):
class Meta:
model = MetaData
fields = 'all'
def validate_shipping_address_zip_code(self, value):
if len(value) > 5:
raise serializers.ValidationError("Zip code must be 5 characters or less.")
return value
def create(self, validated_data):
unparseable_info = {}
for field, value in self.initial_data.items():
try:
validated_data[field] = self.fields[field].run_validation(value)
except serializers.ValidationError as e:
unparseable_info[field] = value
instance = MetaData.objects.create(**validated_data)
if unparseable_info:
instance.unparseable_info = unparseable_info
instance.save()
return instance
def validate_shipping_address_zip_code(self, value):
if value >= 5:
return value
else:
raise serializers.ValidationError("Message Here")
there's much more validators in serializer look into more detail
https://www.django-rest-framework.org/api-guide/serializers/

How can i pass validated data to another custom validator class in DRF?

I Have this kind of serializer.py
class PostSerializer(serializers.ModelSerializer):
title = serializers.CharField(validators=[TitleValidator()])
slug = serializers.CharField(validators=[SlugsValidator()], max_length=100, required=False)
and i have two class validators for this fields
class TitleValidator:
MIN_TITLE_LENGTH = 20
def __call__(self, title: str):
if len(title) < self.MIN_TITLE_LENGTH:
raise ValidationError(f"Min title length is {self.MIN_TITLE_LENGTH}")
return title
class SlugsValidator:
def __call__(self, slug):
# Get title here
return slug
How can I pass the validated title into SlugValidator class?
I've tried to pass data directly to the TitleValidator instance, but the only thing I can get is the field itself, not an actual value.
Another way was to pass data inside validate() method, but seems like custom class validators are executed first, and i' getting an error that the title argument is not provided.
Is there any way I can achieve this?
It seems like it possible to get all fields if you define all class validators inside Meta class, but I'm wondering if it is possible without using Meta class

How to auto populate a read-only serializer field in django rest framework?

I have a question regarding django rest framework.
Most of the time, I have a serializer which has some read-only fields. For example, consider this simple model below:
class PersonalMessage(models.Model):
sender = models.ForeignKey(User, related_name="sent_messages", ...)
recipient = models.ForeignKey(User, related_name="recieved_messages", ...)
text = models.CharField(...)
def __str__(self) -> str:
return f"{self.text} (sender={self.sender})"
In this model, the value of sender and recipient should be automatically provided by the application itself and the user shouldn't be able to edit those fields. Alright, now take a look at this serializer:
class PersonalMessageSerializer(serializers.ModelSerializer):
class Meta:
model = PersonalMessage
fields = '__all__'
read_only_fields = ('sender', 'recipient')
It perfectly prevents users from setting an arbitrary value on the sender and recipient fields. But the problem is, when these fields are marked as read-only in the serializer, the serializer will completely ignore all the values that are passed into the constructor for these fields. So when I try to create a model, no values would be set for these fields:
PersonalMessageSerializer(data={**request.data, 'sender': ..., 'recipient': ...) # Won't work
What's the best way to prevent users from setting an arbitrary value and at the same time auto-populate those restricted fields in django rest framework?
Depending on how you get those two objects, you can use the serializer's save method to pass them, and they will automatically be applied to the object you are saving:
sender = User.objects.first()
recipient = User.objects.last()
serializer = PersonalMessageSerializer(data=request.data)
message = serializer.save(sender=sender, recipient=recipient)
The kwargs should match the field names in your model for this to work. For reference, have a look here
You able to override the serializer context like this;
PersonalMessageSerializer(data={**request.data, context={'sender': sender, 'recipent': recipent})
and catch the context inside serializer.
class PersonalMessageSerializer(serializers.ModelSerializer):
class Meta:
model = PersonalMessage
fields = '__all__'
read_only_fields = ('sender', 'recipient')
def validate(self, attrs):
attrs = super().validate(attrs)
attrs['sender'] = self.context['sender']
attrs['recipent'] = self.context['recipent']
return attrs
now serializer.validated_data it must returns sender and recipent.
From the question it is not possible to understand what field(s) of the relationship with sender and recipient you want to interact with, but a general answer can be found in the Serializer relations section of Django REST documentation.
Long story short, if you want to interact with one field only, you can use SlugRelatedField, which lets you interact with the target of the relationship using only one of its fields.
If it just the id, you can use PrimaryKeyRelatedField.
If you want to interact with more than one field, the way to go is Nested Relationships. Here you can specify a custom serializer for the target relationship, but you will have to override the create() method in your PersonalMessageSerializer to create the object from your relationship, as nested serializers are read-only by default.
So this is how you can make set a default on create but read only after in DRF. Although in this solution it wont actually be readonly, it's writable, but you now have explicit control on what the logged in user can write, which is the ultimate goal
Given the model
class PersonalMessage(models.Model):
sender = models.ForeignKey(User,...)
recipient = models.ForeignKey(User,..)
text = models.CharField(...)
You would first create your own custom default (I will show an example for only one field)
# Note DRF already has a CurrentUserDefault you can also use
class CurrentSenderDefault:
requires_context = True
def __call__(self, serializer_field):
return serializer_field.context['request'].user
def __repr__(self):
return '%s()' % self.__class__.__name__
Next you make your own field, that knows whats up with the filter.
This queryset prevents people from setting a value they are not allowed to. which is exactly what you want
class SenderField(serializers.PrimaryKeyRelatedField):
def get_queryset(self):
user = self.context['request'].user
if user:
queryset = User.objects.filter(id=user.id)
else:
queryset = User.objects.none()
return queryset
Finally on the serialiser you go
class PersonalMessageSerializer(serializers.ModelSerializer):
sender = SenderField(default=CurrentSenderDefault())
recipient = ...
class Meta:
model = PersonalMessage
fields = '__all__'
read_only_fields = ('sender', 'recipient')

DRF expose model field in two separate read-write fields on the REST API

I'm using Python 2.7, DRF 3.1.3, Django 1.4.21 (I know it's old but it's a large codebase, one day we'll migrate). The real-world business case is more complicated than this, here is the simplified version.
I have a model:
class Person(models.Model):
foo = models.CharField(max_length=2, blank=True, null=True)
On the REST API I want to expose two fields, both of them is derived from foo. Both fields should be writable, and both fields would set the foo field in a way when I write into it.
Try 1: bar is unfortunately read-only
class PersonSerializer(serializers.ModelSerializer):
bar = serializers.SerializerMethodField()
class Meta:
model = Person
fields = ('id', 'foo', 'bar')
def get_bar(self, obj):
return get_bar(obj.foo) # not relevant now what get_bar does
Try 2: trying to fake a field somehow
class PlaceholderCharField(serializers.CharField):
def to_representation(self, obj):
pass
def to_internal_value(self, data):
pass
class PersonSerializer(serializers.ModelSerializer):
bar = PlaceholderCharField()
class Meta:
model = Person
fields = ('id', 'foo', 'bar')
def to_representation(self, instance):
data = super(PersonSerializer, self).to_representation(instance)
data['bar'] = get_bar(instance.foo)
return data
def to_internal_value(self, data):
instance = super(PersonSerializer, self).to_internal_value(data)
if data.has_key('bar') and not data.has_key('foo'):
instance.foo = get_foo(data['bar']) if data['bar'] else None
return instance
This latter one errors out obviously complaining that the Person model doesn't have a bar field. Which is true. How to solve this problem? I can handle setting/getting the foo and bar in the serializer's to_representation and to_internal_value. I just want a way in DRF to specify a field, which only exists on the REST API side, and doesn't have a field associated on the model end. I can take care of the transformations. And that field should be read-write, otherwise I could just solve it with a SerializerMethodField.
Tweak your first attempt a little and you can alias that field, no problem:
class PersonSerializer(serializers.ModelSerializer):
# vvvvvvvvv
bar = serializers.CharField(
source='foo', required=False
)# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
class Meta:
model = Person
fields = ('id', 'foo', 'bar')
I'm still using DRF 2.4.4 for the better nested, multi-object nested serializer support, but I use that method for aliasing a URL and its associate object all the time to deal with the way Angular JS compares objects in some of its controls. e.g.
class SensorSerializer(serializers.HyperlinkedModelSerializer):
location_obj = SensorLocationSerializer(source='location',read_only=True,required=False)
class Meta:
model = Sensor
fields = ('url', 'id', 'name', 'serial_number', 'location',
'location_obj', 'active')
I'm unsure of your usecase, it seems a little odd, but why don't you just add a property on your model. For example,
#property
def bar(self):
"""
Used for DRF only.
"""
return None # or self.foo
Then add it to your fields in the serializer (no need to use PlaceholderCharField)
fields = ('id', 'foo', 'bar')
The rest will work as you have it.

Categories

Resources