How to auto change db input on IntegrityError in Django? - django

I'm using a model form to let a user put Data into the database. This form excludes the user field so the user cannot change it himself.
class Meta:
model = Server
exclude = ["user","name", "quirky"]
Instead the value of the user field will be put in after I call .save(commit=False).
if neuer_server_form.has_changed() and neuer_server_form.is_valid():
neuer_server = neuer_server_form.save(commit=False)
neuer_server.user = request.user
neuer_server.name = slugify(neuer_server.name)
neuer_server.save()
Because of that the user field is excluded from validation. Now there is a unique_together between the user field and another char field.
class Meta:
unique_together = ('user', 'name',)
Because the user field is excluded there will be no validation for the unique_together. So when saving the instance there can be an IntegrityError.
Where I'm stuck:
So my first Idea was to check the db if the CharField already exists and if so just change it by adding a number and check again. But if I do this counting upwards an attacker might insert a lot of similar strings so my server has to do this checking indefinitely long.
Possible Solutions:
So for me there would be two acceptable solutions: Either change the CharFields value to something that definitely does not exist yet without trying a lot first. Or make the validation fail and throw the form back to the user.
What I tried:
I think the second would be ideal, but since I'm using model formset and cannot pass the request user to the form it's not possible for me to do that:
Django's ModelForm unique_together validation
Instead I was wondering if it was possible to add self made errors to a form while looping through a formset.
Somewhat like this pseudo code:
for form in formset.forms:
if form.is_valid():
server_name = form.cleaned_data.get("name","")
if Server.objects.get(user=request.user,name=server_name).count():
formset.forms[form].errors += "here is my own error message"
formset.forms[form].fields["name"].errors += "this field is wrong"
Any ideas how to solve that? Thank you for help if you read until here :)

if request.method == 'POST':
server = Server(user=request.user)
form = ServerForm(request.POST, instance=server)
if form.is_valid():
f = form.save()
f.name = slugify(f.name)
f.save()
else:
messages.error(request,
' '.join([v[0].__str__() for k, v in form.errors.items()]))

Related

How to send new form fields with data from Django views.py?

I am adding a new field named "user" to the "order" model. I did make migrations.
(Bonus question is: why in the sql3db column "user_id" was made, instead of "user"? But ok, I change form.fields['user'] to form.fields['user_id'], as the machine wants...)
I remove unneeded fields from the form in forms.py, and I try to add this fields in views.py (because I don't know how to send "user" to the forms.py, and I think it more secure like this).
And as I can understand - my new fields is absent for "order", when I use form.save().
This field can't be null by design.
Your form has no user field, hence that will not work. What you can do is alter the object wrapped in the form with:
if form.is_valid():
form.instance.user = request.user
form.instance.email = request.user.email
order = form.save()

Django model validation

I'm facing a validation problem.
I need to use form validation and model validation together, but django (1.10) doesn't seem to like this.
Here is a short version of my setup:
class MyModel(models.Model):
fk = models.ForeignKey('ap.Model')
foo = models.CharField(max_length=12)
def clean(self):
if self.fk.som_field != self.foo:
raise ValidationError("This did not validate")
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ('fk',)
def view(request):
instance = MyModel(foo='bar')
form = MyModelForm(data=request.POST, instance=instance)
if form.is_valid():
# process
# redirect
# display template
So I have some model field validation in the model itself.
I need this here because it is re-used in other non-form related parts of my application.
And I have some user input validation in the form.
In this case, checking that the provided fk is valid and exists.
But when the form is validated and the user provided 'fk' is not valid, the form is rejected.
But the form also calls MyModel.full_clean add more model validation.
The problem is that calling MyModel.clean() without having any data in the field fk will raise a RelatedObjectDoesNotExist exception.
How can I do this the proper way ?
I feel that MyModel.full_clean() should not be called by the form until the form itself is valid to ensure that the model is validated with at least correct field types in it.
I could embed my MyModel.clean operations in a try/except that would catch a RealtedObjectDoesNotExist, but it has a bad code smell for me.
The fact that MyModel.fk exists should be ensured by the first form layer validation.
As you have found, the model form performs the instance validation (by calling self.instance.full_clean()`) whether or not the form is valid.
You could prevent this behaviour (I might try to override the _post_clean, but I think it's simpler to take account of the behaviour in the clean method.
Rather than catching the exception, it would be simpler to check that self.fk_id is not None before accessing self.fk.
class MyModel(models.Model):
fk = models.ForeignKey('ap.Model')
foo = models.CharField(max_length=12)
def clean(self):
if self.fk_id is not None and self.fk.som_field != self.foo:
raise ValidationError("This did not validate")

Proper Model Field For One To One Database Relationships In Django

My issue is that my app is not allowing me to update a OneToOneField field. Here's my explanation of what I'm trying to do.
I am building an inventory app that keeps track of instruments that have been loaned to students. There will always be a one-to-one database relationship between students and instruments. So an individual student can't ever have more than one instrument and vice versa.
I therefore created an Intrument model that looks like this:
class Instrument(models.Model):
instrument_type = models.CharField(max_length=100)
needs_repairs = models.BooleanField()
inventory_id = models.CharField(max_length=100)
student = models.OneToOneField(Student, null=True, blank=True, default = None)
I have created a form that allows me to update existing students, and I'm trying to use as much built-in stuff as possible so that I don't need to re-write validation code or HTML. So I'm using a ModelForm object and validating my input using the is_valid() method.
Here's an example of a POST request to update an instrument:
csrfmiddlewaretoken=xyUBhVuQZus6XmeV2DhCmpJHwIXVmdHm&instrument_type=Viola&inventory_id=abcde&student=3
Please note that the only field with a uniqueness constraint is student.
So finally, here's the problem: when I call the is_valid() method it always fails with an error saying that the student has already been assigned to an instrument.
My first thought was to use the framework to add some pre-validation code that didn't error if the student pkey didn't change. This certainly seems easy enough, but it seems to be a bit hacky to me. I assumed that one-to-one relationships would "just work" like all of the other Model fields and that no special validation would be required.
But then I read the API docs for the OneToOneField class and it doesn't seem to address one-to-one database relationships - it seems to address one-to-one OO relationships. So I may be using the wrong Model field type all together. And since this is such a simple app, I'm not performing a ton of OO modeling - I'm just worried about proper data modeling :-)
So am I using the wrong field, or is the "proper" way to fix this to add pre-validation code to my Student model?
Updates From Comments
Here's the closest thing that I have to a stack trace:
>>> data = {'instrument_type': 'Viola', 'inventory_id': 'abcde', 'student': 3, 'repairer': 1}
>>> form = InstrumentForm(data)
>>> form.is_bound
True
>>> form.is_valid()
False
>>> form.errors
{'student': [u'Instrument with this Student already exists.']}
I use a single view method to display Instrument detail and update a single Instrument. Here's that:
def instrument_detail(request, instrument_id):
try:
instrument = Instrument.objects.get(pk=instrument_id)
except Instrument.DoesNotExist:
raise Http404
# Default if not a POST
form = InstrumentForm(instance=instrument)
if request.method == 'POST':
form = InstrumentForm(request.POST)
if form.is_valid():
form.save()
return HttpResponseRedirect(reverse('instruments.views.instruments_index'))
# otherwise...
t = loader.get_template('instruments/details.html')
c = RequestContext(request, {
'instrument': instrument,
'form': form,
})
return HttpResponse(t.render(c))
You're not passing the instance when instantiating the form on POST.
if request.method == 'POST':
form = InstrumentForm(request.POST, instance=instrument)

Problem in django ChoiceField

I have come across a problem when I submit an empty radio input. If I select a choice, the form functions fine; however, if I leave it blank, I get the following error --
MultiValueDictKeyError at /
Key 'like' not found in <QueryDict:...
I've tried several solutions, including using a dict.get on the mentioned field 'like' and deleting the column in the database -- it seems to be a problem in the forms module.
Here is my code:
In forms.py --
from django import forms
class PictureForm(forms.Form):
like = forms.ChoiceField(widget=forms.RadioSelect(), choices=(
[('yes','yes'), ('no','no'),]),)
name = forms.CharField()
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
And in views.py
def index2(request):
if request.method == 'POST':
form = PictureForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
Picture.objects.create(like=cd['like'], name=cd['name'], email=cd['email'], message=cd['message'])
return HttpResponseRedirect ('/thanks/')
else:
form = PictureForm()
return render_to_response('index2.html', {'form':form}, context_instance=RequestContext(request))
I would like to have it so there is obviously no error when the radio submitted blank -- and
1) how to allow a blank submission, and
2) how to prompt an error message.
In addition, the validations are not working on this form (I've done previous tutorials, and this is the first time the validations aren't automatically working).
Thank you.
Picture.objects.create(like=cd['like'], [...])
You are trying to access the cd dictionary with a key that doesn't exist, since the field has no value.
Try putting in an if/else statement:
if like in cd:
Picture.objects.create(like=cd['like'], [...])
Also, it's not clear if you're using a ModelForm as Thierry suggested, but if so, you might need to add the parameters blank=True, null=True to your Model field creation, in order to allow for null values.
What does your Picture model look like? Does your model have any restrictions on the uniqueness of multiple columns? It looks like you are using your form to create a model, did you read the doc on ModelForm?
Your form can be simplified to:
from django.forms import ModelForm
class PictureForm(ModelForm):
like = forms.ChoiceField(widget=forms.RadioSelect(), choices=(
[('yes','yes'), ('no','no'),]),)
class Meta:
model = Picture
In your view:
if form.is_valid():
form.save()

Django: Can I restrict which fields are saved back to the database using forms?

I have a form that I use to display several fields from a record to the user. However, the user should not be able to update all the fields that are displayed. How do I enforce this? It would nice if I could specify which fields to save when calling form.save, but I couldn't get this to work. Here's some of the code:
obj = get_object_or_404(Record, pk=record_id)
if request.method == 'POST':
form = forms.RecordForm(request.POST, instance=obj)
if form.is_valid():
form.save()
I don't think using exclude or fields in the form's Meta definition will work as this will only display the fields the user is allowed to update.
You can override the form's save() method:
class MyModelForm(forms.ModelForm):
def save(self, commit=True):
if self.instance.pk is None:
fail_message = 'created'
else:
fail_message = 'changed'
exclude = ['field_a', 'field_b'] #fields to exclude from saving
return save_instance(self, self.instance, self._meta.fields,
fail_message, commit, construct=False,
exclude=exclude)
Option 1: exclude those fields, and use your template to display the data that should not be changed completely outside of the form itself. It sounds to me like they're not really part of the form, if the user can't change them.
Option 2: In a Django form, how do I make a field readonly (or disabled) so that it cannot be edited?
take this answer to mark your fields as read only... but understand there's no server side security here, so you would want to do something like getting the target model before you update it, and update those offending form fields to the existing data, before you save the form.