I want to update a model using djangorestframework. I don't need to update all fields, so I use PATCH. However, in my form I also have an image field (called 'logo'), which is required for my model. When I try to 'patch' the object and I don't choose a new image for that field, drf throws an error ('logo': 'This field is required').
I know that when using django forms, file fields get a special treatment, meaning that if they already have a value, submitting the form with an empty filefield will just keep the old value. Is there any way to do that using djangorestframework serializers?
Some code for better understanding:
# models.py
class Brand(models.Model):
name = models.CharField(_('name'), max_length=250)
logo = models.ImageField(upload_to='brands/')
# serializers.py
class BrandSerializer(serializers.ModelSerializer):
class Meta:
model = Brand
fields = (
'id',
'name',
'logo',
)
# detail.html
<form method="post" enctype="multipart/form-data">
{%csrf_token%}
<input name="name" type="text" maxlength="30" value="{{ brand.name }}"/>
<input name="logo" type="file" accept="image/*"/>
<input name="_method" type="hidden" value="PATCH">
<input type="submit" value="Update"/>
</form>
The best I could come up with for now was to delete the logo entry from my request.DATA before calling the serializer. I am curious if anyone knows a better solution. Thanks.
Try Link hope fully you will get solution.
Or See this cade, hope fully this will work for you.
class ImageSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = Brand
fields = ('name', 'logo')
def saveImage(self, imgFileUri):
#parse dataUri and save locally, return local path
return 'somewhereOverTheBlah'
def restore_fields(self, data, files):
reverted_data = {}
if data is not None and not isinstance(data, dict):
self._errors['non_field_errors'] = ['Invalid data']
return None
for field_name, field in self.fields.items():
"""
So it is iterating over the fields to serialize, when we find the file field
do something different (in this case look for the fileUri field, handle it and replace
it inside of the reverted_data dictionary with the intended file field
"""
if(field_name == 'file'):
field_name = 'dataUri'
field = fields.CharField()
try:
# restore using the built in mechanism
field.field_from_native(data, files, field_name, reverted_data)
# take the dataUri, save it to disk and return the Path
value = reverted_data[field_name]
path = self.saveImage(value)
# set the file <Path> property on the model, remove the old dataUri
reverted_data['file'] = path
del reverted_data[field_name]
except ValidationError as err:
self._errors[field_name] = list(err.messages)
else:
field.initialize(parent=self, field_name=field_name)
try:
field.field_from_native(data, files, field_name, reverted_data)
except ValidationError as err:
self._errors[field_name] = list(err.messages)
return reverted_data
Related
I have two models, Task and TaskNote, and I am trying to get the current Task's primary key entered as an initial value in the TaskNoteForm when the form is called from the current Task's view.
On the task detail view there is a link to a form where the user can write a note that will appear on the detail view. I would like the form to have the Task instance primary key set as a foreign key in TaskNote (for example, as model attribute "subject").
My solution was to get the URL using a 'HTTP_REFERER' request and parse the pk from the URL, then pass the pk into context data in the view, and finally enter it as a value in template. For example:
# Models
class Task(models.Model):
id = models.AutoField(
primary_key=True)
...
class TaskNote (models.Model):
...
subject = models.ForeignKey(
Task,
on_delete=models.CASCADE)
...
# view
def get_url_pk(self):
url = self.request.META.get('HTTP_REFERER')
t = url.split('/')
pk = int(t[-1])
return pk
class TaskNotesCreate(CreateView):
template_name='tasknotes_new.html'
form_class = TaskNoteForm
model = TaskNote
def get_context_data(self, **kwargs):
context = super(TaskNotesCreate, self).get_context_data(**kwargs)
context['pk'] = get_url_pk(self)
return context
def get_success_url(self, **kwargs):
obj = self.object
return reverse('task_view', kwargs={'pk': obj.subject_id})
# template
...
<div class="form-group">
<label for="exampleInput" class="required">
<input type="hidden" name="subject" value="{{ pk }}">
</div>
This works fine, however...
I have learned that HTTP_REFERER is not the preferred method as it can be disabled in some browsers. I can't seem to find the what the preferred method is in the docs, so if anyone could help me on that, that would be great. But also, before I spend a lot to time hacking something else together, I am wondering...
Is there a better, more acceptable way to pass the pk to the form???
Thanks
Rather than editing the template, you can do it the following way:
Update your TaskNoteForm so it has hidden input field for the value you want to pass along (this is a normal form field with widget=HiddenInput()).
On the view, overwrite get_initial to pass the object's value as the pk. This should look like
def get_initial():
initial = super().get_initial()
initial.update(
{"pk": self.object.pk}
)
return initial
I have a model with user as 1 field (Foreign Key) and one other field skill_group. I need to make sure the user does not add duplicate skill groups so I added a UniqueConstraint. This is working as the system errors out with IntegrityError at /skillgroup/create/
duplicate key value violates unique constraint "unique_skillgroup" - How do I catch this exception and notify user if duplicate; otherwise save it?
New to Django/Python/Postgres and I thought I could handle it by overriding the save() function, but there is no access to user which is part of the check and I have read this should not be handled here. Is there a try/save catch/message I should be employing? I have tried a few things with no luck. I have seen similar questions on here, but they have not helped. Any help is appreciated.
models.py
class SkillGroup(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
skill_group = models.CharField(max_length=35)
sequence = models.IntegerField(default=999)
class Meta:
constraints = [
models.UniqueConstraint(fields=['user', 'skill_group'], name='unique_skillgroup'),
]
def __str__(self):
return self.skill_group
def get_absolute_url(self):
return reverse('skillgroup-list')
views.py
class SkillGroupCreateView(LoginRequiredMixin, CreateView):
model = SkillGroup
fields = ['skill_group']
def form_valid(self, form):
form.instance.user = self.request.user
form.instance.sequence = SkillGroup.objects.filter(user=self.request.user).order_by('sequence').last().sequence + 1
return super().form_valid(form)
skillgroup_form.html
{% extends "recruiter/baseskills.html" %}
{% load crispy_forms_tags %}
{% block content%}
<div class="content-section">
<form method="post">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Skill Group</legend>
{{ form|crispy }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">Add Skill Group</button>
</div>
</form>
</div>
{% endblock content%}
I want to either catch the exception and save the record if not a duplicate or put message on screen saying "Skill Group already exists" and leave user on create page. Also, I could remove the UniqueConstraint and handle with code if that is the best solution.
You are inadvertently bypassing Django's form validation here and then trying to save invalid input to the database, which is why Django is feeding back an ugly IntegrityError from the database instead of handling the error gracefully.
If you submit a duplicate User and SkillGroup in your form, your CreateView will helpfully return the error message back into your form template:
"Skill group with this User and Skill group already exists."
But it can only do this if you include a User field in your form. I assume you have excluded User to keep the form template tidy, but that prevents Django's form validation from checking if the combination already exists.
To get around this, add User to your form field as a hidden input. I don't think that's possible using CreateView's behind-the-scenes magic, so you'll need to create a SkillGroupForm to handle that.
# forms.py
from django import forms
from .models import SkillGroup
class SkillGroupForm(forms.ModelForm):
class Meta:
model = SkillGroup
fields = ('user', 'skill_group')
widgets = {
'user': forms.HiddenInput,
}
# views.py
from .forms import SkillGroupForm
class SkillGroupCreateView(LoginRequiredMixin, CreateView):
model = SkillGroup
form_class = SkillGroupForm
def get_initial(self):
return {'user': self.request.user}
def form_valid(self, form):
form.instance.sequence = SkillGroup.objects.filter(user=self.request.user).order_by('sequence').last().sequence + 1
return super().form_valid(form)
The get_initial method passes the request.user as an initial value into the hidden form field, so no user input is needed.
Try to validate skill_group field inside your form class. Define clean_skill_group method like in docs.There you can get queryset of all SkillGroup objects related to your User (like here) and then compare skills. But you need to push somehow your User object or user_id (to get then User object) to the form before form.is_valid() will be called (or call one more time form.is_valid() after). Then show form errors in your html template.
class YourForm(forms.ModelForm):
....some fields, Meta....
def clean_skill_group(self):
your_user_object = ....
previously_created_skills = your_user_object.skill_group_set.all()
skill_input = self.cleaned_data["skill_group"]
if skill_input in previously_created_skills:
raise forms.ValidationError(("This skill group is already exist"), code="invalid") # I suppose you are using model form
I have a form that I want to be pre-populated with data from a model instance when a user is attempting to update an existing database record. When the form is rendered none of the radio buttons pre-selected even though a model instance has been passed to the ModelForm. In a much larger form than listed below, all of the fields except the radio buttons are pre-populated with the correct data from the model instance. How do I get the correct radio buttons pre-selected?
My Models:
class TicketType(models.Model):
type = models.CharField(max_length=15, unique=True)
def __unicode__(self):
return self.type.title()
class TestTicket(models.Model):
ticket_type = models.ForeignKey(TicketType, to_field='type')
My Form
class TestTicketForm(ModelForm):
ticket_type = ModelChoiceField(TicketType.objects.all(),
widget=RadioSelect,
empty_label=None)
class Meta:
model = TestTicket
fields = ['ticket_type']
My View
def test_ticket_update(request, ticket_num=None):
# initialize an update flag to distinguish between a request
# to add a new ticket or an update to an existing ticket.
update_requested = False
ticket_instance = None
if ticket_num:
# This is a request to update a specific ticket.
# Attempt to retrieve the ticket or show a 404 error.
# If a ticket is retrieved, it is locked for editing
# by using 'select_for_update()' to prevent a race condition.
ticket_instance = get_object_or_404(
TestTicket.objects.select_for_update(),pk=ticket_num)
update_requested = True
if request.method == 'POST':
form = TestTicketForm(request.POST, instance=ticket_instance)
if form.is_valid():
ticket = form.save(commit=False)
ticket.save()
return HttpResponseRedirect('/tickets/')
else:
if update_requested:
# This is a requested to update an existing ticket.
# Bind the ticket data to a form.
form = TestTicketForm(instance=ticket_instance)
else:
form = TestTicketForm()
return render(request, 'ticket_tracker/ticket_update.html',
{ 'form': form, 'ticket': ticket_instance})
My Template
{% block content %}
<div class="container">
<form action="/tickets/test-ticket/{{ ticket.id }}/" method="post">
{% csrf_token %}
{{ form.as_p }}
<div class="form-group">
<button type="submit">Save</button>
</div>
</form>
</div>
{% endblock content %}
It appears this has been answered here before. In my model I used the to_field argument in the creation of the ForeignKey field, but the ModelChoiceField is expecting to use the id when the 'initial' value is passed to it. There are several options to fix this in my example including:
Remove the to_field parameter from the ForeignKey field in the model.
When creating the form instance in the view, set the 'initial' parameter for the field using the field's id from the model instance, e.g.,
form = TestTicketForm(request.POST,
instance=ticket_instance,
initial={'ticket_type': instance.ticket_type.id)
Set the form field's initial value in the forms __init__() method. Again this uses the field's id from the model instance. For example:
class TestTicketForm(ModelForm):
ticket_type = ModelChoiceField(TicketType.objects.all(),
widget=RadioSelect,
empty_label=None)
def __init__(self, *args, **kwargs):
super(TestTicketForm, self).__init__(*args, **kwargs)
if self.instance is not None:
self.initial['ticket_type'] = self.instance.ticket_type.id
Option #1 above would require a schema and data migrations in my database. Options #2 and #3 are similar but I chose option #3 since it makes my view code slightly cleaner.
I am building a webapp which will be used by a company to carry out their daily operations. Things like sending invoices, tracking accounts receivable, tracking inventory (and therefore products). I have several models set up in my various apps to handle the different parts of the web-app. I will also be setting up permissions so that managers can edit more fields than, say, an office assistant.
This brings me to my question. How can I show all fields of a model and have some that can be edited and some that cannot be edited, and still save the model instance?
For example, I have a systems model for tracking systems (we install irrigation systems). The system ID is the primary key, and it is important for the user to see. However, they cannot change that ID since it would mess things up. Now, I have a view for displaying my models via a form using the "form.as_table". This is efficient, but merely spits out all the model fields with input fields filling in the values stored for that model instance. This includes the systemID field which should not be editable.
Because I don't want the user to edit the systemID field, I tried making it just a label within the html form, but django complains. Here's some code:
my model (not all of it, but some of it):
class System(models.Model):
systemID = models.CharField(max_length=10, primary_key=True, verbose_name = 'System ID')
systemOwner = models.ForeignKey (System_Owner)
installDate = models.DateField()
projectManager = models.ForeignKey(Employee, blank=True, null=True)
#more fields....
Then, my view for a specific model instance:
def system_details(request, systemID):
if request.method == 'POST':
sysEdit = System.objects.get(pk=systemID)
form = System_Form(request.POST, instance=sysEdit)
if form.is_valid():
form.save()
return HttpResponseRedirect('/systems/')
else:
sysView = System.objects.get(pk=systemID)
form = System_Form(instance=sysView)
return render_to_response('pages/systems/system_details.html', {'form': form}, context_instance=RequestContext(request))
Now the html page which displays the form:
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Save Changes">
<input type="button" value="Cancel Changes" onclick="window.location.href='/systems/'">
</form>
So, what I am thinking of doing is having two functions for the html. One is a form for displaying only those fields the user can edit, and the other is for just displaying the content of the field (the systemID). Then, in the view, when I want to save the changes the user made, I would do:
sysValues = System.objects.get(pk=SystemID)
form.save(commit = false)
form.pk = sysValues.sysValues.pk (or whatever the code is to assign the sysValues.pk to form.pk)
Is there an easier way to do this or would this be the best?
Thanks
One thing you can do is exclude the field you don't need in your form:
class System_Form(forms.ModelForm):
class Meta:
exclude = ('systemID',)
The other is to use read-only fields: http://docs.djangoproject.com/en/1.3/ref/contrib/admin/#django.contrib.admin.ModelAdmin.readonly_fields as #DTing suggessted
To make a field read only you can set the widget readonly attribute to True.
using your example:
class System_Form(ModelForm):
def __init__(self, *args, **kwargs):
super(System_Form, self).__init__(*args, **kwargs)
self.fields['systemID'].widget.attrs['readonly'] = True
class Meta:
model = System
or exclude the fields using exclude or fields in the class Meta of your form and display it in your template if desired like so:
forms.py
class System_Form(ModelForms):
class Meta:
model = System
exclude = ('systemID',)
views.py
def some_view(request, system_id):
system = System.objects.get(pk=system_id)
if request.method == 'POST':
form = System_Form(request.POST, instance=system)
if form.is_valid():
form.save()
return HttpResponse('Success')
else:
form = System_Form(instance=system)
context = { 'system':system,
'form':form, }
return render_to_response('some_template.html', context,
context_instance=RequestContext(request))
some_template.html
<p>make changes for {{ system }} with ID {{ system.systemID }}</p>
<form method='post'>
{{ form.as_p }}
<input type='submit' value='Submit'>
</form>
In my view for editing friends I'm checking if form is valid, and then save the form. But somehow the data are not updated. Why my updated form is not saved ? It is 100% valid, since I've checked it earlier.
My form :
class FriendForm(forms.ModelForm):
first_name = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=50)), label="First name")
last_name = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=50)), label="Last name")
pid = forms.RegexField(regex=r'^\d{11}', max_length=11 ,widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=50)))
image = forms.ImageField(label="Image", required=False)
street = forms.CharField(widget=forms.TextInput(attrs=dict(attrs_dict, maxlength=50)), label="Street")
number = forms.CharField(widget=forms.TextInput, label="House/flat number")
code = forms.RegexField(regex=r'^\d{2}[-]\d{3}', max_length=6, widget=forms.TextInput(attrs=attrs_dict), label="Postal code")
city = forms.CharField(widget=forms.TextInput, label="City")
The view :
def edit_friend(request, id):
userprofile = UserProfile.objects.get(user=request.user)
friend = get_object_or_404(Friend, id=id)
if friend.friend_of.id!=userprofile.id:
raise Http404
if request.method == 'POST':
form = FriendForm(request.POST, request.FILES, instance=friend)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('user_profile',))
else:
form = FriendForm(instance=friend)
return render_to_response('user/data_operations/edit_friend.html', {
'form':form, 'user':request.user,
}, context_instance=RequestContext(request))
Template :
<form method="post" action="." enctype="multipart/form-data">
<table>
{{ form.as_table }}
<tr>
<td> </td>
<td>
<input type="submit" class="submit" name="submit" value="Save" />
</td>
</tr>
</table>
</form>
I'd need to see the full code for your form to really answer this (can you include it?) but here are some initial thoughts:
Is FriendForm a subclass of django.forms.ModelForm? If so, there's no need to override the save method -- especially since all you're doing here is getting the returned new object, saving it (again), and returning it (again) -- additional processing with no additional benefit.
If FriendForm isn't a subclass of ModelForm, how is it bound to database data? What class is it inheriting from?
UPDATE:
ModelForms aren't connected directly to the database -- they are a shortcut for creating HTML forms for interacting with database models -- i.e. classes inheriting from django.models.Model. You do that by creating a Meta class within your ModelForm.
With a ModelForm, you don't need to manually specify the fields (django does that automatically for you) unless you want to override specific behaviors. You do have to tell django which database Model you're like to use. If you've already defined a database Model, use it; if not, try this:
# in models.py
from django import models
class Friend(models.Model):
first_name = models.CharField( "see <http://docs.djangoproject.com/en/dev/ref/models/fields/> to adjust your syntax" )
... your other fields ...
# in forms.py
from django.forms import ModelForm
from my_project.my_app.models import Friend
class FriendForm(ModelForm):
class Meta:
model = Friend
That's it! Now your FriendForm should work properly. See http://docs.djangoproject.com/en/dev/topics/forms/modelforms/ for more information on using ModelForms.
Alternatively, you don't need to use a ModelForm at all. You could add the following save method to your existing FriendForm:
def save(self):
if self.is_valid():
from myproject.myapp.models import Friend
new_friend = Friend(**self.cleaned_data)
new_friend.save()
return new_friend
... the essence here is that you're importing the Friend model, which encapsulates the database storage behaviors. This is basically what the ModelForm creates automatically for you, but not as robust -- so I recommend using ModelForm.
sometimes, if you add decorators like #transaction.commit_on_success during development, it prevents saving the form if there is even a single error.
I think you may be overriding your model fields with those form fields.
check this. Normally when you have a modelform all you need to do is define a meta class and pass the extra settings you need for that particular form (rendered fields, their widgets, their labels, etc).
You should set action attribute for your form tag. And set url name in urls.py.
Like <form action="{% url 'edit_friend' %}" method="post">
urls.py:
(...'/some-edit-url/', views.edit_friend, name='edit_friend'),