hidden field changes value when saving form django - python

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')?

Related

django-countries how to add serializer field

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.

How to use a different unique identifier of Django ForeignKey field in ModelForm

I have the following Models:
class Book(models.Model):
name = models.CharField(max_length=128)
isbn = models.CharField(max_length=13, unique=True)
available = models.BooleanField(default=True)
class Borrow(models.Model):
date = models.DateTimeField(auto_now_add=True)
book = models.ForeignKey(Book)
and the following ModelForm:
class CreateBorrowForm(forms.ModelForm):
class Meta:
model = Borrow
fields = ['book']
def clean_book(self):
book = self.cleaned_data['book']
try:
return Book.objects.get(id=book, available=True)
except Book.DoesNotExist:
raise ValidationError('Book does not exist or it is unavailable')
I would like to have a form that expects the isbn field of the Book model, instead of id. As the isbn field is unique, it does make sense. In the clean_book method I would need to do a little change to have the following line:
return Book.objects.get(isbn=book, available=True)
The problem is that I cannot find an approach to force the form to use a different unique identifier. In my specific case, this is required to avoid brute force enumerating over numerical IDs.
You'd need to use a custom field for that, and override the save() method instead of the clean__field():
class CreateBorrowForm(forms.ModelForm):
book_isbn = forms.CharField()
class Meta:
model = Borrow
fields = ['book_isbn']
def save(self, commit=True):
instance = super().save(commit=False)
book_isbn = self.cleaned_data['book_isbn']
try:
book = Book.objects.get(isbn=book_isbn, available=True)
instance.book = book
except Book.DoesNotExist:
raise ValidationError('Book does not exist or it is unavailable')
if commit:
instance.save()
return instance

How to handle formset for multiple instances?

I'm building a match system for a Tournament Manager. I have a "Match" model and "Set" model (code down below). First, I'd like to have a form that regroups all sets related to one match, how can I do that ? Secondly, how can I handle this if I have several matchs in my template ?
models.py
class Match(models.Model):
isFinished = models.BooleanField(default=False)
team1Win = models.BooleanField(default=False)
team2Win = models.BooleanField(default=False)
phase = models.ForeignKey(Phase, default=None, on_delete=models.CASCADE)
teams = models.ManyToManyField(Team, default=None, blank=True)
class Set(models.Model):
timeSet = models.DecimalField(max_digits=5, decimal_places=2, blank=True, null=True)
scoreTeam1 = models.IntegerField(null=True)
scoreTeam2 = models.IntegerField(null=True)
match = models.ForeignKey(Match, default=None, on_delete=models.CASCADE)
models.py
class SetUpdateForm(forms.ModelForm):
class Meta:
model = Set
fields = [
'scoreTeam1',
'scoreTeam2',
'match',
]
EDIT:
I created my formset, etc... All works perfectly good but I want to make some validation before submitting the formset, how can I do that ?
What you can do is to create an inlineformset that will map the Match and all related Set.
First you need the Match and all related Set:
#I assume you have the match pk from the url
def match_formset_view(request,pk):
match = get_object_or_404(Match, pk = pk)
#get all the related Set
sets = match.set_set.all()
#create the inline formset
MatchSetFormset = forms.inlineformset_factory(
Match,
Set,
form=SetUpdateForm,
min_num=1,
extra=0,
can_delete=True
)
#populate the formset accordingly
formset = MatchSetFormset(request.POST or None,instance=match, queryset= sets,prefix='sets')
#validate the formset
if formset.is_valid():
#do something then save
formset.save()
else:
#do other things.
Be aware that you can't save the formset if you didn't save the Match instance first(for creation).

Django Rest Framework: Saving ForeignKey inside OneToOne model

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

Can't edit but can add new inline in Django admin

Here are my models
class Note():
note = models.TextField(null=False, blank=False, editable=True)
user = models.ForeignKey(to=User, null=True, blank=True)
content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
object_id = models.PositiveIntegerField()
content_object = GenericForeignKey("content_type", "object_id")
And an inline I created this model to incorporate in any admin is below
class NoteInline(GenericTabularInline):
model = Note
extra = 0
What I need here is, I want to see all the current notes but don't want the logged in user to edit them. At the moment user can edit old and add new. So here is what I did,
class NoteInline(GenericTabularInline):
model = Note
extra = 0
def get_readonly_fields(self, request, obj=None):
if obj and 'change' in request.resolver_match.url_name:
return ['note', 'user', ]
else:
return []
But now if user adds new note he sees a disabled (not editable) note text ares. However user can see old fields not editable.
How to implement this functionality?
I am having the same inquiry.
However, I do not care if the fields in the inline are "read only" or not. I just do not want them changed once they are created.
For this purpose, I created a NoteForm in forms.py which raises a validation error if the instance has changed while it has initial data:
class NoteForm(forms.ModelForm):
def clean(self):
if self.has_changed() and self.initial:
raise ValidationError(
'You cannot change this inline',
code='Forbidden'
)
return super().clean()
class Meta(object):
model = Note
fields='__all__'
admin.py:
class NoteInline(GenericTabularInline):
model = Note
extra = 0
form = NoteForm

Categories

Resources