I develop a Django project with inlineformset nested using FormView.
I first develop my form using CreateView/UpdateView and it works but when I use FormView I got an error
'UtilisateurUpdateView' object has no attribute 'object'
Why I can get access 'object' when I use UpdateView but not with FormView ?
I have read that it could come from override method but here doesn't seems to be the case ?
forms.py
NAME = Thesaurus.options_list(2,'fr')
ACCESS = Thesaurus.options_list(3,'fr')
ApplicationFormset = inlineformset_factory(
UtilisateurProjet, Application, #Utilisateur, Application,
fields=('app_app_nom','app_dro'),
widgets={
'app_app_nom': forms.Select(choices=NAME),
'app_dro': forms.Select(choices=ACCESS)
},
extra=3,
can_delete=True,
)
class UtilisateurProjetUpdateForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
self.request = kwargs.pop("request")
super(UtilisateurProjetUpdateForm, self).__init__(*args, **kwargs)
self.fields["pro_ide"] = forms.ModelChoiceField(queryset = PROJETS, label = "Nom projet", widget = forms.HiddenInput(), initial = Projet.objects.get(pro_ide=self.request['projet']))
self.fields["uti_ide"] = forms.ModelChoiceField(queryset = UTILISATEURS, label = "Nom, prénom de l'utilisateur", widget = forms.Select, initial = Utilisateur.objects.get(uti_ide=self.request['utilisateur']))
class Meta:
model = UtilisateurProjet
fields = ('pro_ide','uti_ide',)
views.py
class UtilisateurUpdateView(FormView):
template_name = 'project/utilisateurprojet_form.html'
form_class = UtilisateurProjetUpdateForm
def get_form_kwargs(self):
kwargs = super(UtilisateurUpdateView, self).get_form_kwargs()
kwargs['request'] = dict(utilisateur = self.kwargs['pk'], projet = self.kwargs['projet'])
# kwargs['request'] = self.request
# print('projet',self.kwargs['projet'])
# print('utilisateur',self.kwargs['pk'])
return kwargs
def get_context_data(self, **kwargs):
data = super(UtilisateurUpdateView,self).get_context_data(**kwargs)
# print('projet',self.kwargs['projet'])
# print('utilisateur',self.kwargs['pk'])
instance = UtilisateurProjet.objects.get(pro_ide=self.kwargs['projet'],uti_ide=self.kwargs['pk'])
data['projet'] = Projet.objects.get(pro_ide=self.kwargs['projet'])
data['utilisateur_app'] = Utilisateur.objects.get(uti_ide=self.kwargs['pk'])
if self.request.POST:
data["utilisateur"] = self.request.user.username
data["user_profil"] = self.request.session.get('user_profil')
data["application"] = ApplicationFormset(self.request.POST, instance=instance)
else:
data["application"] = ApplicationFormset(instance=instance)
return data
def form_valid(self, form):
# Setting commit to False will return the object without saving it to the Database.
self.object = form.save(commit=False)
context = self.get_context_data()
application = context["application"]
user_profil = context["user_profil"]
if user_profil == 'Investigateur':
self.object.uti_val = 0 # demande modifiée par investigateur -> repasse à non validée + envoie de mail
elif user_profil == 'Moniteur':
self.object.uti_val = 1 # demande validée par moniteur -> validée + envoie de mail
self.object.uti_val_dat = timezone.now()
else:
self.object.uti_val = 0
# After doing our own changes to the object, we can save it.
self.object.save()
if application.is_valid():
# Not sure what is happening here, but this statement does nothing by itself.
# form.instance = self.object
application.save()
return super().form_valid(form)
def get_success_url(self):
return reverse("project:index")
FormView and DetailView are different classes from different packages: django.views.generic.edit.FormView and django.views.generic.detail.DetailView respectively.
From the docs of DetailView:
While this view is executing, self.object will contain the object that
the view is operating upon.
FormView doesn't have an object property, because it doesn't necessarily work with an object.
However, since you're using ModelForms, you should be able to access the Form's object after calling form.save():
def form_valid(self, form):
# Setting commit to False will return the object without saving it to the Database.
self.object = form.save(commit=False)
context = self.get_context_data()
application = context["application"]
user_profil = context["user_profil"]
self.object.uti_val = 1
self.object.uti_val_dat = timezone.now()
else:
self.object.uti_val = 0
# After doing our own changes to the object, we can save it.
self.object.save()
if application.is_valid():
# Not sure what is happening here, but this statement does nothing by itself.
form.instance = self.object
application.save()
return super().form_valid(form)
Related
I have a problem with this code:
PhoneNumberFormSet = inlineformset_factory(Person, PhoneNumber, fields=('phone_number',),
can_delete=False, extra=1)
EmailAddressFormSet = inlineformset_factory(Person, EmailAddress, fields=('email_address',),
can_delete=False, extra=1)
class PersonCreateView(CreateView):
form_class = PersonForm
success_url = reverse_lazy('person-list')
template_name = 'contacts/person_create.html'
def get_context_data(self, **kwargs):
data = super(PersonCreateView, self).get_context_data(**kwargs)
data['phone_formset'] = PhoneNumberFormSet(self.request.POST or None)
data['email_formset'] = EmailAddressFormSet(self.request.POST or None)
return data
def form_valid(self, form):
context = self.get_context_data()
phone_formset = context['phone_formset']
email_formset = context['email_formset']
with transaction.atomic():
o = form.save() # <--- this object is saved even when formsets below are not valid
condition = phone_formset.is_valid() and email_formset.is_valid()
if not condition:
return render(self.request, self.template_name, self.get_context_data())
phone_formset.instance = o
phone_formset.save()
email_formset.instance = o
email_formset.save()
return super(PersonCreateView, self).form_valid(form)
The transaction.atomic() is saving obcject "o" even when phone_formset or email_formset are not valid and view renders forms with errors (object should not be saved)
It doesn't work because atomic transactions roll back only if exception is raised. In your case, you just return normal response object, which doesn't trigger roll back and hence commits to database.
More about atomic transactions in django docs.
Change your code to something like this (didn't confirm it's working, but this type of example is in the linked docs):
from django.core.exceptions import ValidationError
def form_valid(self, form):
context = self.get_context_data()
phone_formset = context['phone_formset']
email_formset = context['email_formset']
try:
with transaction.atomic():
o = form.save() # <--- this object is saved even when formsets below are not valid
condition = phone_formset.is_valid() and email_formset.is_valid()
if not condition:
raise ValidationError
phone_formset.instance = o
phone_formset.save()
email_formset.instance = o
email_formset.save()
except ValidationError:
return render(self.request, self.template_name, self.get_context_data())
return super(PersonCreateView, self).form_valid(form)
I have one form and formset. I want to update them at the same time (with the same submit). Forms are rendered correct (with good data), but updated data is not written to db. Nether forms validation works. When it is one form it works perfect. Where could be the problem?
My view class:
class EventAdminPage(UpdateView):
form_class = CreateEventForm
second_form_class = modelformset_factory(EventTime, form = EventTimeForm, min_num=1, validate_min=True, extra=3)
model = Event
template_name = 'eventAdmin.html'
def get_context_data(self, **kwargs):
context = super(EventAdminPage, self).get_context_data(**kwargs)
event_times = EventTime.objects.filter(event = self.object).exclude(start_time = None)
context['participants'] = Participant.objects.filter(event = self.object)
context['event_times'] = EventTime.objects.filter(event = self.object).exclude(start_time = None)
context['event_time_formset_helper'] = event_time_formset_helper = EventTimeFormsetHelper()
context['formset'] = self.second_form_class(prefix='eventtime', queryset=event_times)
context['form'] = self.form_class(instance = self.object)
return context
def post(self, request, **kwargs):
self.object = self.get_object()
event_times = EventTime.objects.filter(event = self.object).exclude(start_time = None)
if 'update_event_form' in request.POST:
form = self.form_class(instance=self.get_object())
formset = self.second_form_class(queryset=event_times)
if form.is_valid() and formset.is_valid():
form.save()
formset.save()
return HttpResponseRedirect('/event-admin-%s' %self.kwargs['event_id'])
else:
return self.render_to_response(
self.get_context_data(form=form, formset=formset))
def get(self, request, **kwargs):
self.object = Event.objects.get(pk=self.kwargs['event_id'])
form = self.form_class
formset = self.second_form_class
context = self.get_context_data(object=self.object, form=form, formset=formset)
return self.render_to_response(context)
def get_object(self, queryset=None):
obj = Event.objects.get(pk=self.kwargs['event_id'])
return obj
class EditShipment(LoginRequiredMixin, FormView):
template_name = "add-edit-shipment.html"
model = Shipment
form_class = ShipmentForm
success_url = lazy(reverse,str)("shipment_listing")
def get_context_data(self, **kwargs):
context = super(EditShipment,self).get_context_data(**kwargs)
context['shpid'] = self.kwargs.get('shpid',None)
context['key'] = 'edit'
return context
def get_initial(self):
ship_obj = Shipment.objects.get(id=int(self.kwargs.get('shpid',None)))
stdr_obj = Shipment_Truck_Driver_Relation.objects.get(shipment = ship_obj)
return {'origin_address':ship_obj.origin_address,\
'destination_address':ship_obj.destination_address,\
'total_weight':ship_obj.total_weight,'pick_up':ship_obj.pick_up,\
'delivery':ship_obj.delivery,'amount':ship_obj.amount,\
'truck':stdr_obj.truck,'driver':stdr_obj.driver}
def get_form(self, form_class):
return form_class(initial=self.get_initial())
def form_valid(self,form):
import ipdb;ipdb.set_trace()
userprofile_obj = User_Profile.objects.get(user = self.request.user)
compuser_obj = Company_Users.objects.get(user_profile = userprofile_obj)
self.f = form.save(commit=False)
self.f.total_weight = self.request.POST.get('total_weight')
self.f.pickup_cutoff = self.request.POST.get('pick_up')
self.f.delivery_cutoff = self.request.POST.get('delivery')
self.f.customer, self.f.created_by = compuser_obj.company, userprofile_obj
self.f.save()
return super(EditShipment, self).form_valid(form)
Here form_valid is not saving but its not throwing any error, also the object values are not updated.
How to save in def form_valid?
My form isn't saving the models that I need it to. My form:
class RewardForm(forms.Form):
quantity = forms.IntegerField(max_value=10, min_value=1, label=_('quantity'), initial=1)
reward = forms.CharField(max_length=50, label=_('reward'))
reward_denomination = forms.ModelChoiceField(queryset=Reward_Denomination.objects.all(), widget=forms.RadioSelect)
def clean_reward(self):
data = self.cleaned_data.get('reward')
try:
reward = Reward.objects.get(reward_name=data)
except ObjectDoesNotExist:
raise forms.ValidationError(_('Reward does not exist'), code='invalid')
return data
def clean_reward_denomination(self):
data = self.cleaned_data.get('reward_denomination')
try:
denomination = Reward_Denomination.objects.get(denomination=data)
except ObjectDoesNotExist:
raise forms.ValidationError(_('Denomination does not exist'), code='invalid')
return data
def save(self, request, commit=True):
user = request.user
data = self.cleaned_data
'try:
post_reward = data['reward']
post_denomination = data['reward_denomination']
quantity = data['quantity']
except LookupError:
raise Http404
reward = Reward.objects.get(reward_name=post_reward)
denomination = Reward_Denomination.objects.get(denomination=post_denomination)
user_points = Points.objects.filter(affiliate__id=user.id).aggregate(total_points=Sum('points'))
user_points = user_points['total_points']
try:
total_cost = (quantity * denomination.cost)
except ArithmeticError:
raise Http404
quote_price = -total_cost
if user_points >= total_cost:
reward_order = Points.objects.create(affiliate=user, points=quote_price, from_reward=True, from_offer=False)
status_coded = Status_Code.objects.create(short_name="Pending", name="The order is currently being reviewed", description="The order is in queue")
redeem_order = Redeem.objects.create(affiliate=user, status_code=status_coded, quantity=quantity, reward=reward, price=total_cost)
return reward_order
My Views:
class Reward_Detail(DetailView):
model = Reward
slug_field = 'reward_slug'
context_object_name = 'reward'
template_name = 'omninectar/reward.html'
#Detail Stuff
class RedeemReward(SingleObjectMixin, FormView):
template_name = 'omninectar/reward.html'
slug_field = 'reward_slug'
form_class = RewardForm
model = Reward
def post(self, request, *args, **kwargs):
self.object = self.get_object()
return super(RedeemReward, self).post(request, *args, **kwargs)
def get_success_url(self):
return reverse('omni:reward_confirmation')
class RewardBeautify(View):
def get(self, request, *args, **kwargs):
view = Reward_Detail.as_view()
return view(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
view = RedeemReward.as_view()
return view(request, *args, **kwargs)
So I initially thought that the FormView would handle the form processing (validate, and, if valid, run form.save(), etc). I'm following the FormView, SingleObjectMixin example on the Django website. I don't receive any errors when I try and submit the form, but no objects are created either. I've tried defining a form_valid method that runs the save method, I've tried putting it inside the post method in the formview, etc. Can anyone spot the error/errors? Thanks!
I'm new to view classes too and I had almost the same problem with Django 1.6.
You should add
def form_valid(self, form):
form.save()
return super(RedeemReward, self).form_valid(form)
method overriding to your RedeemReward class. This worked for me.
If you look to Django source code, you will see that there is no form saving in FormView class inheritance chain.
I am not sure if this will help or not, but I had issues finding code showing how to save the data from a FormView model. This is what I came up with. I hope it helps and you can apply it to your code.
forms.py
class JobCreateForm(forms.Form):
title = forms.CharField(label='Job Title', max_length=500)
number = forms.IntegerField(label='Job Number: ')
comps = forms.ModelMultipleChoiceField(label='Comparable Sales',
required=False, queryset=m.ComparableSale.objects.all())
details = forms.CharField(label='Job Details:', max_length=200,
required=False, widget=forms.Textarea(attrs={'rows':6, 'cols':20}))
Views.py
class JobCreateView(generic.FormView):
template_name = 'form_templates/createjob_form.html'
form_class = f.JobCreateForm
model = models.Job
success_url = '/'
def form_valid(self, form):
comps = form.cleaned_data['comps']
title = form.cleaned_data['title']
number = form.cleaned_data['number']
details = form.cleaned_data['details']
job = models.Job(title=title, number=number, details=details)
job.save()
print(comps)
if comps != []:
job.comps.add(*comps)
return super(JobCreateView, self).form_valid(form)
You can write your own ModelFormView using the mixins provided by Django (specifically, the ModelFormMixin). Then your form will be saved on a successful post.
from django.views.generic.base import TemplateResponseMixin
from django.views.generic.edit import ModelFormMixin, ProcessFormView
class BaseModelFormView(ModelFormMixin, ProcessFormView):
pass
class ModelFormView(TemplateResponseMixin, BaseModelFormView):
pass
I am having trouble posting data in an admin form. Some fields get empty after clicking the save button, others not (the inlines are keeping their data). See the images for a better explanation of the problem.
Entering some data:
After clicking on [save]...
Validation error! (The model is not saved)
My ModelForm is quite simple: I am just changing the form field for one of the m2m model fields.
class News(models.Model):
departments = models.ManyToManyField(Department, blank=True, related_name='news', through='NewsDepartmentMembership')
research_groups = models.ManyToManyField(Group, blank=True, related_name='news', through='NewsGroupMembership')
related_news = models.ManyToManyField('self', blank=True, symmetrical=False)
people_involved = models.ManyToManyField(Person, blank=True, related_name='news')
title = models.CharField(_('Title'), max_length=255)
slug = models.SlugField(_('Slug'), unique_for_date='pub_date',
help_text=_('A slug is a short name which uniquely identifies the news item for this day'), )
excerpt = RichTextField(_('Excerpt'), blank=True)
content = RichTextField(_('Content'), blank=True)
is_published = models.BooleanField(_('Published'), default=False)
pub_date = models.DateTimeField(_('Publication date'), default=datetime.datetime.now)
is_feat = models.BooleanField(
_('Featured'), default=False,
help_text=_('Administrators may use this checkbox to promote news to the main news page')
)
published = PublishedNewsManager()
objects = models.Manager()
featured = FeaturedNewsManager()
class NewNewsForm(forms.ModelForm):
class Meta:
model = News
related_news = forms.ModelMultipleChoiceField(
queryset=News.objects.none(),
required=False,
widget=FilteredSelectMultiple(
verbose_name=_('articles'),
is_stacked=False,
)
)
def __init__(self, user=None, *args, **kwargs):
super(NewNewsForm, self).__init__(*args, **kwargs)
if hasattr(user, 'is_superuser'):
self.fields['related_news'].queryset = get_objects_for_user(user, ('news.change_news',)).filter(
is_published__exact=True).order_by('pub_date')
else:
self.fields['related_news'].queryset = News.published.order_by('pub_date')
if self.instance.pk:
self.fields['related_news'].initial = self.instance.related_news.all()
def save(self, commit=True):
news = super(NewNewsForm, self).save(commit=False)
if commit:
news.save()
if news.pk:
news.related_news = self.cleaned_data['related_news']
self.save_m2m()
return news
The ModelAdmin is quite complicated, it inherits 2 ModelAdmins. The 1st one comes from the django-modeltranslation package. I adapted the 2nd one from here, just to perform cross inline formsets validation. It's working in other packages without any problem (at least until today). I just have to override the method is_cross_valid to define the cross inline validation
class NewsAdmin(TranslationAdmin, ModelAdminWithInlines):
fields = ('title', 'slug_en', 'slug_nb', 'excerpt', 'content', 'is_published', 'pub_date', 'related_news', 'is_feat')
inlines = (DepartmentsNewsInline, GroupsNewsInline, PersonNewsInline)
form = NewNewsForm
prepopulated_fields = {'slug_en': ('title',), 'slug_nb': ('title',)}
class Media:
js = (
'http://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js',
'http://ajax.googleapis.com/ajax/libs/jqueryui/1.10.1/jquery-ui.min.js',
'modeltranslation/js/tabbed_translation_fields.js',
)
css = {
'screen': ('modeltranslation/css/tabbed_translation_fields.css',),
}
def queryset(self, request):
return get_objects_for_user(request.user, (u'news.change_news', ))
def formfield_for_manytomany(self, db_field, request=None, **kwargs):
if db_field.name == 'related_news':
return get_objects_for_user(request.user, ('news.change_news',), ).order_by('pub_date')
return super(NewsAdmin, self).formfield_for_manytomany(db_field, request, **kwargs)
def is_cross_valid(self, request, form, formsets):
valid = True
# my validation code goes here...
return valid
def get_readonly_fields(self, request, obj=None):
if not request.user.groups.filter(name__exact='administration') and not request.user.is_superuser:
return ('is_feat', )
return ()
def has_change_permission(self, request, obj=None):
if super(NewsAdmin, self).has_change_permission(request, obj):
return True
return request.user.has_perm('news.change_news', obj)
This is my ModelAdminWithInlines, almost the same as here:
class ModelAdminWithInlines(ModelAdmin):
"""
Cross formsets validation. See https://stackoverflow.com/a/2746735
"""
def is_cross_valid(self, request, form, formsets):
"""
To perform cross-formset validation.
Should be overriden in every inheriting class.
"""
return True
def add_view(self, request, form_url='', extra_context=None):
"""The 'add' admin view for this model."""
model = self.model
opts = model._meta
if not self.has_add_permission(request):
raise PermissionDenied
ModelForm = self.get_form(request)
formsets = []
inline_instances = self.get_inline_instances(request, None)
if request.method == 'POST':
form = ModelForm(request.POST, request.FILES)
if form.is_valid():
new_object = self.save_form(request, form, change=False)
form_validated = True
else:
form_validated = False
new_object = self.model()
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(data=request.POST, files=request.FILES,
instance=new_object,
save_as_new="_saveasnew" in request.POST,
prefix=prefix, queryset=inline.queryset(request))
formsets.append(formset)
# if all_valid(formsets) and form_validated:
formsets_validated = all_valid(formsets)
cross_validated = self.is_cross_valid(request, form, formsets)
if formsets_validated and form_validated and cross_validated:
self.save_model(request, new_object, form, False)
self.save_related(request, form, formsets, False)
self.log_addition(request, new_object)
return self.response_add(request, new_object)
else:
# Prepare the dict of initial data from the request.
# We have to special-case M2Ms as a list of comma-separated PKs.
initial = dict(request.GET.items())
for k in initial:
try:
f = opts.get_field(k)
except FieldDoesNotExist:
continue
if isinstance(f, ManyToManyField):
initial[k] = initial[k].split(",")
if ModelForm.Meta.model._meta.module_name == 'news' and ModelForm.Meta.model._meta.object_name == 'News':
form = ModelForm(initial=initial, user=request.user) # here I am injecting the user object into the form
# just to be able to access the objects for this user
else:
form = ModelForm(initial=initial)
prefixes = {}
for FormSet, inline in zip(self.get_formsets(request), inline_instances):
prefix = FormSet.get_default_prefix()
prefixes[prefix] = prefixes.get(prefix, 0) + 1
if prefixes[prefix] != 1 or not prefix:
prefix = "%s-%s" % (prefix, prefixes[prefix])
formset = FormSet(instance=self.model(), prefix=prefix,
queryset=inline.queryset(request))
formsets.append(formset)
adminForm = AdminForm(
form, list(self.get_fieldsets(request)),
self.get_prepopulated_fields(request),
self.get_readonly_fields(request),
model_admin=self)
media = self.media + adminForm.media
inline_admin_formsets = []
for inline, formset in zip(inline_instances, formsets):
fieldsets = list(inline.get_fieldsets(request))
readonly = list(inline.get_readonly_fields(request))
prepopulated = dict(inline.get_prepopulated_fields(request))
inline_admin_formset = InlineAdminFormSet(
inline, formset, fieldsets, prepopulated, readonly, model_admin=self
)
inline_admin_formsets.append(inline_admin_formset)
media = media + inline_admin_formset.media
context = {
'title': _('Add %s') % force_text(opts.verbose_name),
'adminform': adminForm,
'is_popup': "_popup" in request.REQUEST,
'media': media,
'inline_admin_formsets': inline_admin_formsets,
'errors': AdminErrorList(form, formsets),
'app_label': opts.app_label,
}
context.update(extra_context or {})
return self.render_change_form(request, context, form_url=form_url, add=True)
I really can't understand the source of my problem. Can anyone spot the issue?
Thanks!
UPDATE
As #GarryCairns suggested, I've tried to save an object from the shell. No problem with that.
>>> n = News.objects.create(title_en='test', slug_en='test', content_en='test')
>>> n.id
4
UPDATE 2:
Non translated fields are empty as well :-/
UPDATE 3:
>>> n = News()
>>> n
<News: >
>>> n.title_en = 'test'
>>> n.slug_en
>>> n.slug_en = 'test'
>>> n.content_en = 'blah blah'
>>> n.save()
>>> n.id
5
FIXED
It seems that both setting the ModelMultipleChoiceField queryset manually from the form init() and the NewsAdmin.formfield_for_manytomany() method were messing up the whole form data...