I'm trying to find some kind of canonical reference for this problem where I use the follow type model:
class MyPreferencesForm(forms.Form):
MyGenderPref = forms.MultipleChoiceField(choices=GENDER_CHOICES, widget=forms.CheckboxSelectMultiple())
and the view
def GoPreferences(request):
if request.method == "POST":
f = MyPreferencesForm(request.POST)
if f.is_valid():
model_instance = f.save(commit=False)
model_instance.save()
f.save_m2m()
return redirect('/')
else:
f = MyPreferencesForm()
return render(request, "mypreferences.html", {'form': f})
However I tried to follow what I thought was sensible and include the many to many. But I still cannot make this work. My error is the following:
Exception Type: AttributeError
Exception Value:
'MyPreferencesForm' object has no attribute 'save'
The error emanates from line containing model_instance = f.save(commit=False)
save is available only for forms inherited from ModelForm objects. Here, you are inheriting form forms.Form Hence the error.
Either inherit the form from forms.ModelForm and specify a Meta attribute
class MyPreferencesForm(forms.ModelForm):
MyGenderPref = forms.MultipleChoiceField(choices=GENDER_CHOICES, widget=forms.CheckboxSelectMultiple())
class Meta:
model = MyModel
You can read more on ModelForms here - You might have to modify your form slightly based on your specific requirements.
OR
model_instance = MyModel.objects.create(**form.cleaned_data) #manage manually
If your m2m tables in your models.py uses through then you'll need to manage the saving manually using object.create and you can't use save_m2m() or save()
Related
I overrode the GET request function handler in this class based view. In my else statement, I need to pass, as context data, the form that the class naturally creates (if I had not overridden the GET function). How can I do that?
I did not create a form at forms.py to create a form for the Post model. I let the create class based view handle the form creation for me. So, how can I get this form and pass as context data.
The only way I can think of doing this is creating a function based view and avoid using this class based view in this circumstance.
class PostCreateView(LoginRequiredMixin, CreateView):
model = Post
fields = ["title", "content", "payment_option", "price"]
def get(self, request):
card_set = BillingProfile.objects.get(user=request.user).card_set.all()
if not card_set.exists():
# The user does NOT have an inserted payment method.
return redirect("/billing/payment-method?next=/post/new/")
else:
# The user DOES have an inserted payment method.
form = "???"
return render(request, "posting/post_form.html", {"form":form})
You could use the method the class provides, which is self.get_form().
But this actually wouldn't be the right thing to do. What your should really do is to delegate to the default implementation of get and let it do what it normally would.
if not card_set.exists():
# The user does NOT have an inserted payment method.
return redirect("/billing/payment-method?next=/post/new/")
else:
return super().get(request)
Edited: I am trying to update the value of a single field inside one of Django's objects. Here is the code:
class TodoCompleteView(generic.DetailView):
queryset = Todo.objects.all()
def get_object(self):
# Call the superclass
object = super(TodoCompleteView, self).get_object()
# Record the last accessed date
object.todo_completed = True
object.save()
# Return the object
return object
However, I keep getting an error:
TemplateDoesNotExist at /8/complete
list/todo_detail.html
How can avoid this? I simply want this view to flip a certain value in DB.
You inherit view from DetailView class, which by default is to view some models and not to change. Also, apparently, you use GET request to change the data. This is the wrong approach.
Alternatively I advise you to try to make inherit your view from SingleObjectMixin and View and manually create a handler for POST request.
I would rewrite your example like this:
from django.views.generic import View
from django.views.generic.detail import SingleObjectMixin
class TodoCompleteView(SingleObjectMixin, View):
model = Todo
def post(self, *args, **kwargs):
self.object = self.get_object()
self.object.todo_completed = True
self.object.save(update_fields=('todo_completed', ))
return HttpResponse(status=204)
P.S. you get the error, because DetailView subclassed from SingleObjectTemplateResponseMixin which tries to render the template called <model_name>_detail.html.
I want to use a Django (1.4) modelformset in which, when the formset is loaded the forms will be arranged by a exam_date field I have in my model. To do this I've created the following simple BaseModelFormSet
class BaseExamDateFormSet(BaseModelFormSet):
def __init__(self, *args, **kwargs):
super(BaseExamDateFormSet, self).__init__(*args, **kwargs)
self.queryset = models.ExamDate.objects.all().order_by('exam_date')
As you can see I've changed the queryset as proposed in the
django docs (later I'll also change its clean method but it doesn't matter right now). After that I am using it in my view to create the formset:
ExamDateFormSet = modelformset_factory(
models.ExamDate,
exclude =('authority', ),
can_delete=True,
extra=1,
formset = forms.BaseExamDateFormSet
)
...
formset = ExamDateFormSet()
My problem is that when the formset is rendered the data in the forms is always in the same ordering (probaby by id) regardless of the value of the order_by attribute :(
Should I try setting a default order by in my ExamDate model ? I really don't like that solution though :(
TIA !
After some more searching I found a solution !
I ignored the queryset attribute and added the following method to the BaseExamDateFormSet:
def get_queryset(self):
return models.ExamDate.objects.all().order_by('exam_date')
However I don't know yet why the queryset attribute isn't working.
Easier still, in your above code you could erase your formset= argument in modelformset_factory call and instantiate your formset as:
formset = ExamDateFormSet(queryset=models.ExamDate.objects.order_by('exam_date'))
I have read over the Forms and Formset Django documentation about 100x. To make this very clear, this is probably the first time I've ever used super() or tried to overload/inherit from another class (big deal for me.)
What's happening? I am making a django-model-formset in a view and I am passing it to a template. The model that the formset is inheriting from happens to be a ManyToMany relationship. I want these relationships to be unique, so that if my user is creating a form and they accidentally choose the same Object for the ManyToMany, I want it to fail validation.
I believe I have written this custom "BaseModelFormSet" properly (via the documentation) but I am getting a KeyError. It's telling me that it cannot find cleaned_data['tech'] and I am getting the KeyError on the word 'tech' on the line where I commented below.
The Model:
class Tech_Onsite(models.Model):
tech = models.ForeignKey(User)
ticket = models.ForeignKey(Ticket)
in_time = models.DateTimeField(blank=False)
out_time = models.DateTimeField(blank=False)
def total_time(self):
return self.out_time - self.in_time
The customized BaseModelFormSet:
from django.forms.models import BaseModelFormSet
from django.core.exceptions import ValidationError
class BaseTechOnsiteFormset(BaseModelFormSet):
def clean(self):
""" Checks to make sure there are unique techs present """
super(BaseTechOnsiteFormset, self).clean()
if any(self.errors):
# Don't bother validating enless the rest of the form is valid
return
techs_present = []
for form in self.forms:
tech = form.cleaned_data['tech'] ## KeyError: 'tech' <-
if tech in techs_present:
raise ValidationError("You cannot input multiple times for the same technician. Please make sure you did not select the same technician twice.")
techs_present.append(tech)
The View: (Summary)
## I am instantiating my view with POST data:
tech_onsite_form = tech_onsite_formset(request.POST, request.FILES)
## I am receiving an error when the script reaches:
if tech_onsite_form.is_valid():
## blah blah blah..
Isn't the clean method missing a return statement ? If I remember correctly it should always return the cleaned_data. Also the super call returns the cleaned_data so you should assign it there.
def clean(self):
cleaned_data = super(BaseTechOnsiteFormset, self).clean()
# use cleaned_data from here to validate your form
return cleaned_data
See: the django docs for more information
I used the Django shell to call the forms manually. I found that I was executing the clean() method on all of the forms returned from the view. There were 2 filled out with data, and 2 blank. When my clean() method was iterating through them all, it returned a KeyError when it got to the first blank one.
I fixed my issue by using a try-statement and passing on KeyErrors.
I have a model form:
class SnippetForm(ModelForm):
class Meta:
model = Snippet
exclude = ['author', 'slug']
and I want to be able to edit a particular instance by using this:
def edit_snippet(request, snippet_id):
#look up for that snippet
snippet = get_object_or_404(Snippet, pk=snippet_id)
if request.user.id != snippet.author.id:
return HttpResponseForbidden()
if request.method == 'POST':
form = SnippetForm(data=request.POST, instance=snippet)
if form.is_valid():
form.save()
return HttpResponseRedirect(snippet.get_absolute_url())
else:
form = SnippetForm(instance=snippet)
return render_to_response(SNIPPET_EDIT_TEMPLATE,
{'form':form, 'add':False, 'user':request.user},
RequestContext(request))
Notice that at the line
form = SnippetForm(data=request.POST, instance=snippet)
, I created a form that use the data supplied from the user, and bound it with the instance found using the primary key (received from the url). According to django documentation, when I call save() the existing instance should be updated with POSTED data. Instead, what I see is a new object is created and saved into the database. What went wrong? Thanks a lot.
[Edit] This is really embarrassed. The code indeed has nothing wrong with it. The only thing that messed up the whole thing was the action I put in the template (as I use a same template for add and edit a snippet)....Thanks a lot for your help, really appreciate that.
I don't see why it would happen. What version of django is it?
In any case, you can manually force update passing the corresponding argument.
form = SnippetForm(data=request.POST, instance=snippet, force_update=True)