Is there a way to update a unique field in update view?
I have a model that has a name and age field but when I try to update the age without even changing the value of the name, it returns an error that the name already exists in the database
models.py
class MyModel(models.Model)
name = models.CharField(max_length=200, unique=True)
age = models.IntegerField()
views.py
class MyModelUpdateView(UpdateView):
def get(self):
self.object = self.get_object()
my_model = self.object
form = MyModelForm(instance=my_model)
return self.render_to_response(
self.get_context_data(pk=my_model.pk, form=form)
)
def post(self, request, *args, **kwargs):
self.object = self.get_object()
my_model = self.object
form = MyModelForm(data=request.POST, instance=my_model)
if form.is_valid():
form.save()
return some_url
return self.render_to_response(
self.get_context_data(pk=my_model.pk, form=form)
)
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = (
'name',
'age',
)
def clean(self):
cleaned_data = super().clean()
if MyModel.objects.filter(
active=True, name=cleaned_data.get('name')
).exists():
raise forms.ValidationError('MyModel already exists.')
return cleaned_data
What am I missing here? Thank you.
Since you update a model, and you do not change the name, of course a record with that name already exists: that specific record. You thus should alter the checking code, to:
class MyModelForm(forms.ModelForm):
def clean(self, *args, **kwargs):
cleaned_data = super().clean(*args, **kwargs)
if MyModel.objects.exclude(pk=self.instance.pk).filter(
active=True, name=cleaned_data.get('name')
).exists():
raise forms.ValidationError('MyModel already exists.')
return cleaned_data
class Meta:
model = MyModel
fields = ('name', 'age')
Please do not alter the boilerplate logic of the UpdateView, you can easily implement this with:
class MyModelUpdateView(UpdateView):
form_class = MyModelForm
success_url = 'some url'
That being said, since if you already have set the field to unique=True, then there is no need to implement the check yourself. It seems here, that you already have a unique=True constraint:
class MyModel(models.Model)
name = models.CharField(max_length=200, unique=True)
age = models.IntegerField()
In that case, you can simply let the ModelForm do the work, so then your form looks like:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('name', 'age')
It is only if you want a more sophisticated uniqness (like with active=True?), and you can not represent it (easily) you should do your own validation.
Related
I am trying to save the user automatically when saving new data. I mean I want to save the user id for the user who is posting a new thing (submitting a form) without asking that user to choose his account from the droplist (the droplist from the User ForeignKey)
Try 1 :
models.py
class Feedback(models.Model):
.
.
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
editable=False,
)
def save(self, *args, **kwargs):
self.author = get_user_model().objects.get(id=2) #############
return super().save(*args, **kwargs)
It's working if I hard-coded the user-id get(id=2). How can I make the id dynamic?
Edit:
Try 2 :
I tried also doing it with the view:
models.py
class Feedback(models.Model):
.
.
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
editable=False,
null=True
blank=True
)
# without save()
views.py
class FeedbackForm(ModelForm):
class Meta:
model = Feedback
fields = "__all__"
class FeedbackListView(generics.ListCreateAPIView):
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
from_class = FeedbackForm
permission_classes = [FeedbackPermission,] # I also tried AllowAny
def form_valid(self, form, FeedbackForm):
obj = form.save(commit=False) # I also tried self.obj = ...
obj.author = self.request.user # I also tried self.obj.author = ...
obj.save() # I also tried self.obj.save()
return super().form_valid(form)
But on the second try (with forms), the author field remains null.
ListCreateAPIView don't have method form_valid, look at this
In your class FeedbackListView you need to do something like this:
class FeedbackListView(generics.ListCreateAPIView):
queryset = Feedback.objects.all()
serializer_class = FeedbackSerializer
def perform_create(self, serializer):
# The request user is set as author automatically.
serializer.save(author=self.request.user)
Good evening,
is it possible to change the ModelForm inside my forms.py, so that already known values are saved inside the database? For example:
models.py:
class Customer(models.Model):
name = models.CharField(max_length=255)
class Project(models.Model):
customer = models.ForeignKey(Customer, null=False, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
class Entry(models.Model):
user = ...request.user.id?...
customer = models.ForeignKey(Customer, null=False, on_delete=models.CASCADE)
project= models.ForeignKey(Project, null=False, on_delete=models.CASCADE)
name = models.CharField(max_length=255)
forms.py:
class EntryForm(ModelForm):
class Meta:
model = Entry
fields = '__all__'
def __init__(self, *args, pk, **kwargs):
super().__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(customer_id=pk)
When entering the knew site, I already know about the only possible Customer (pk)! I don't want to place a choicefield inside my knew site, but the customer should be saved inside my database nonetheless! Same goes for the active user (request.user), respectively the id (request.user.id). Can this data be passed into the modelForm as well?
Did someone else also had this problem and might know the solution? What do I have to change inside my modelForm to make it work?
Thanks for all your efforts and a happy weekend to all of you!
You don't have to. You can simply exclude the customer field from the fields:
class EntryForm(ModelForm):
class Meta:
model = Entry
exclude = ['customer']
def __init__(self, *args, pk, **kwargs):
super().__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(customer_id=pk)
Then in the view where you use the EntryForm, you can thus implement this as:
def my_view(request, customer_id):
if request.method == 'POST':
form = EntryForm(request.POST, request.FILES, pk=customer_id)
if form.is_valid():
form.instance.customer_id = customer_id
form.save()
return redirect('name-of-some-view')
else:
form = EntryForm(pk=customer_id)
return render(request, 'name_of_template.html', {'form': form})
You thus can "inject" data, by setting it at form.instance.
If I need to change some field values before saving to the database as I think models method clear() is suitable. But I can't call him despite all my efforts.
For example fields email I need set to lowercase and fields nda I need set as null
models.py
class Vendors(models.Model):
nda = models.DateField(blank=True, null=True)
parent = models.OneToOneField('Vendors', models.DO_NOTHING, blank=True, null=True)
def clean(self):
if self.nda == "":
self.nda = None
class VendorContacts(models.Model):
....
vendor = models.ForeignKey('Vendors', related_name='contacts', on_delete=models.CASCADE)
email = models.CharField(max_length=80, blank=True, null=True, unique=True)
def clean(self):
if self.email:
self.email = self.email.lower()
serializer.py
class VendorContactSerializer(serializers.ModelSerializer):
class Meta:
model = VendorContacts
fields = (
...
'email',)
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
else:
return Response(request.data, status=status.HTTP_200_OK)
As I learned from the documentation
Django Rest Framework serializers do not call the Model.clean when
validating model serializers
In dealing with this problem, I found two ways to solve it.
1. using the custom method at serializer. For my case, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors.objects.create(**validated_data)
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
def validate(self, attrs):
instance = Vendors(**attrs)
instance.clean()
return attrs
Using full_clean() method. For me, it looks like
class VendorsSerializer(serializers.ModelSerializer):
contacts = VendorContactSerializer(many=True)
class Meta:
model = Vendors
fields = (...
'nda',
'contacts',
)
def create(self, validated_data):
contact_data = validated_data.pop('contacts')
vendor = Vendors(**validated_data)
vendor.full_clean()
vendor.save()
for data in contact_data:
VendorContacts.objects.create(vendor=vendor, **data)
return vendor
But in both cases, the clean() method is not called. I really don't understand what I'm doing wrong.
In my case I had the same problem but with validation feature
I used the way below and it works for me (not excludes the way found above):
class CustomViewClass(APIView):
def post(self, request, format=None):
prepared_data_variable = 'some data in needed format'
serializer = CustomSerializer(data=request.data)
if serializer.is_valid(self):
serializer.validated_data['field_name'] = prepared_data_variable
serializer.save()
return Response(data=serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
This string is key for my solution serializer.validated_data['field_name'] = prepared_data_variable
For DRF you can change your serializer before save as below...
First of all, you should check that serializer is valid or not, and if it is valid then change the required object of the serializer and then save that serializer.
if serializer.is_valid():
serializer.object.user_id = 15 # For example
serializer.save()
UPD!
views.py
class VendorsCreateView(APIView):
"""Create new vendor instances from form"""
permission_classes = (permissions.AllowAny,)
serializer_class = VendorsSerializer
def post(self, request, *args, **kwargs):
data = request.data
if data['nda'] == '':
data['nda'] = None
for contact in data['contacts']:
if contact['email']:
print(contact['email'])
contact['email'] = contact['email'].lower()
serializer = VendorsSerializer(data=request.data)
try:
serializer.is_valid(raise_exception=True)
serializer.save()
except ValidationError:
return Response({"errors": (serializer.errors,)},
status=status.HTTP_400_BAD_REQUEST)
To answer your question: just override save() method for your models as written in docs. There you can assign any values to your model instance directly before saving it in database.
Also, you should probably use models.EmailField for your email fields which will get rid of your lower() check.
I have got the code from https://github.com/adandan01/mybook, the code is working fine, even when I have updated it to Django 2. It's very simple project for adding a person in a form, and his/her relatives in the inline form. Everything works but when I add a relative name and forget to add his relationship, and submitted the form, unfortunately, that record will not pass the validation but will give no error messages as well. Django will ignore the entire record. For example, the record for Hawra in the image, will not be saved and Django will remove it. For this simple App there are only two fields to be filled (name and relationship), but I'm working on app with 8 fields, and it will be difficult to lose the data. is there any way to make django do the validation in the formset/subform as long as any fields have data and will ask the user to fill all required fields?
models.py:
class Profile(models.Model):
first_name = models.CharField(max_length=100)
last_name = models.CharField(max_length=100)
created_date = models.DateTimeField(default=timezone.now)
def get_absolute_url(self):
return reverse('profile-update', kwargs={'pk': self.pk})
def __unicode__(self):
return "%s %s" % (self.first_name, self.last_name)
class FamilyMember(models.Model):
profile = models.ForeignKey(Profile, on_delete=models.PROTECT)
name = models.CharField(max_length=100)
relationship = models.CharField(max_length=100)
form.py
class ProfileForm(ModelForm):
class Meta:
model = Profile
exclude = ()
class FamilyMemberForm(ModelForm):
class Meta:
model = FamilyMember
exclude = ()
FamilyMemberFormSet = inlineformset_factory(Profile, FamilyMember,
form=FamilyMemberForm, extra=1)
views.py
class ProfileCreate(CreateView):
model = Profile
fields = ['first_name', 'last_name']
class ProfileFamilyMemberCreate(CreateView):
model = Profile
fields = ['first_name', 'last_name']
success_url = reverse_lazy('profile-list')
def get_context_data(self, **kwargs):
data = super(ProfileFamilyMemberCreate, self).get_context_data(**kwargs)
if self.request.POST:
data['familymembers'] = FamilyMemberFormSet(self.request.POST)
else:
data['familymembers'] = FamilyMemberFormSet()
return data
def form_valid(self, form):
context = self.get_context_data()
familymembers = context['familymembers']
with transaction.atomic():
self.object = form.save()
if familymembers.is_valid():
familymembers.instance = self.object
familymembers.save()
return super(ProfileFamilyMemberCreate, self).form_valid(form)
I found the solution here django inline_formset - form.empty_permitted = False doesn't work
I had to add the following code before if (familymembers.is_valid():...) in the create and update class, so, now Django will show the error if I entered data in the Name field only and will tell me the Relationship field is required.
if familymembers.is_valid() == False:
return self.render_to_response(self.get_context_data(form=form,familymembers=familymembers ))
I have trouble getting the current instance's fields on my UpdateView. How do I get the specific instance based on its id?
views.py
class ShowUpdate(UpdateView):
model = Show
fields = ['description', 'season', 'episode']
def post(self, request, **kwargs):
request.POST = request.POST.copy()
request.POST['description'] = "how to get instance description?" # problem here
request.POST['season'] = 2
return super(ShowUpdate, self).post(request, **kwargs)
models.py
class Show(models.Model):
owner = models.ForeignKey(User, null=True, default=True, related_name='o')
title = models.CharField(max_length=100)
description = models.TextField(default='N/A', blank=True, max_length=250)
season = models.IntegerField(default=0)
episode = models.IntegerField(default=0)
def get_absolute_url(self):
return reverse('show:index')
def __str__(self):
return self.title
Look to the UpdateView docs
This View has method get_object(self, queryset=None)
In you case just need to call it in POST method something like this:
class ShowUpdate(UpdateView):
model = Show
fields = ['description', 'season', 'episode']
def post(self, request, **kwargs):
self.object = self.get_object()
request.POST = request.POST.copy()
request.POST['description'] = self.object.description
request.POST['season'] = 2
return super(ShowUpdate, self).post(request, **kwargs)