I have a Formview and an UpdateView which are conflicting with each other. They are supposed to work together but not in this way. Whenever I try to use the UpdateView the values get passed through the FormView for saving which causes it to send back form validation errors
e.g. 'A video with this Title already exists'.
Forms.py:
class BaseVideoUploadForm(forms.ModelForm):
"""
Form for uploading videos using related to:
:model: 'videos.Video'.
"""
title = forms.CharField(
widget=forms.TextInput(
attrs={'placeholder': 'Blackjack shuffle procedures'}))
description = forms.CharField(
widget=forms.Textarea(attrs={'rows': 3}),
help_text='A description of the content in this video.')
category = forms.ModelChoiceField(
queryset=Category.objects.all(),
required=True)
receiver = forms.ModelMultipleChoiceField(
queryset=EmployeeType.objects.all(),
required=True,
widget=forms.CheckboxSelectMultiple,
help_text='Employees in these groups will be able to view the video.')
video_file = forms.FileField()
thumbnail = forms.ImageField(required=False)
class Meta:
model = Video
fields = (
'title',
'description',
'receiver',
'category',
'video_file',
'thumbnail',
)
widgets = {
'date_time': forms.HiddenInput(),
'sender': forms.HiddenInput(),
'unviewed': forms.HiddenInput(),
'viewed': forms.HiddenInput(),
}
def __init__(self, *args, **kwargs):
super(BaseVideoUploadForm, self).__init__(*args, **kwargs)
self.fields['receiver'].label = "Viewers"
self.fields['receiver'].queryset = EmployeeType.objects.all()
self.fields['category'].queryset = Category.objects.all()
class VideoUploadForm(BaseVideoUploadForm):
send_notifications = forms.BooleanField(
required=False,
help_text='Send viewers a notification about the new video.')
class Meta(BaseVideoUploadForm.Meta):
fields = BaseVideoUploadForm.Meta.fields + ('send_notifications', )
FormView:
class VideoUploadView(FormView):
form_class = VideoUploadForm
success_url = '/videos'
template_name = 'videos/video_form.html'
def get(self, request, *args, **kwargs):
form = self.form_class()
return render(request, self.template_name, {'form': form})
def post(self, request, *args, **kwargs):
form = self.form_class(request.POST, request.FILES)
form.instance.sender = self.request.user
if form.is_valid():
form.save()
# doing stuff
form.save()
messages.success(self.request, self.success_message)
if request.is_ajax():
return JsonResponse({'success': True, 'url': reverse('videos-home')})
else:
return redirect(self.success_url)
else:
if request.is_ajax():
return JsonResponse({'success': False, 'error': form.errors})
else:
return render(request, self.template_name, {'form': form})
UpdateView:
class VideoUpdateView(UpdateView):
model = Video
form_class = VideoUploadForm
Urls.py:
urlpatterns = [
path('', views.VideoListView.as_view(), name='videos-home'),
path('upload/', views.VideoUploadView.as_view(), name='videos-upload'),
path('<int:pk>', VideoDetailView.as_view(), name='videos-detail'),
path('<int:pk>/delete/', VideoDeleteView.as_view(), name='videos-delete'),
path('<int:pk>/viewed/', views.mark_as_viewed, name='mark-as-viewed'),
path('<int:pk>/update/', VideoUpdateView.as_view(), name='videos-update'),
path('<int:pk>/notify', VideoNotificationView.as_view(), name='videos-notify'),
]
How do I get the UpdateView to not rely on the FormView? Or to work with the FormView? It seems all the UpdateView is doing is pointing to the FormView.
Related
In the form for adding a calendar (of the Model "Calendar"), in the "group" field shows a drop-down menu where all the "CalendarGroups" are. I would like this menu not to be there and "group" to be set by default with the "group" id which I pass through the url parameter ("group_id"). How could I solve this?
In models.py:
class CalendarGroups(models.Model):
name = models.CharField(max_length = 155, blank=True, null=True, unique=True)
def __str__(self):
return str(self.name)
#property
def get_html_url(self):
url = reverse('', args=(self.id,))
return f' {self.name} '
class Calendar(models.Model):
name = models.CharField(max_length=200, unique=True)
#created_by
group = models.ForeignKey(CalendarGroups, on_delete = models.CASCADE, default='')
#property
def get_html_url(self):
url = reverse('cal:calendar_view', args=(self.id, self.group))
return f' {self.name} '
In forms.py:
class CalendarGroupsForm(ModelForm):
class Meta:
model = CalendarGroups
fields = ('name',)
def __init__(self, *args, **kwargs):
super(CalendarGroupsForm, self).__init__(*args, **kwargs)
class CalendarForm(ModelForm):
class Meta:
model = Calendar
fields = ('name', 'group')
def __init__(self, *args, **kwargs):
super(CalendarForm, self).__init__(*args, **kwargs)
In views.py:
def group(request, group_id=None):
instance = CalendarGroups()
if group_id:
instance = get_object_or_404(CalendarGroups, pk=group_id)
else:
instance = CalendarGroups()
form = CalendarGroupsForm(request.POST or None, instance=instance)
if request.POST and form.is_valid():
form.save()
return HttpResponseRedirect(reverse('cal:home'))
return render(request, 'cal/form.html', {'form': form})
def calendar(request, group_id=None):
instance = Calendar()
form = CalendarForm(request.POST or None, instance=instance)
if request.POST and form.is_valid():
form.save()
return HttpResponseRedirect(reverse('cal:home'))
return render(request, 'cal/form.html', {'form': form})
In urls.py:
url(r'^home/(?P<group_id>\d+)/calendar/new/$', views.calendar, name='calendar_new')
After consulting the documentation, I solved it by modifying the code as follows:
In views.py:
def calendar(request, group_id=None):
instance = Calendar()
instance = Calendar(group_id=group_id)
form = CalendarForm(request.POST or None, instance=instance)
if request.POST and form.is_valid():
form.save()
return HttpResponseRedirect(reverse('cal:home'))
return render(request, 'cal/form.html', {'form': form})
In forms.py:
class CalendarForm(ModelForm):
class Meta:
model = Calendar
fields = ('name',)
def __init__(self, *args, **kwargs):
super(CalendarForm, self).__init__(*args, **kwargs)
You already have send to the views.calendar the group_id key you need.
Now try to add it to the form before the form.save
Try to modify like this in view.py
def calendar(request, group_id):
instance = Calendar()
form = CalendarForm(request.POST or None, instance=instance)
if request.POST and form.is_valid():
form.group=group_id
form.save()
return HttpResponseRedirect(reverse('cal:home'))
return render(request, 'cal/form.html', {'form': form})
Take note that might override what user can post on CalendarForm and you should consider to hide or make not editable that filed group_id,
Also this is slightly different to what i usually do but it should work
I Post also an example of what works for me:
def calendar(request, group_id):
instance = Calendar()
form = CalendarForm(request.POST or None)
if request.POST and form.is_valid():
instance = form.save(commit=False)
instance.group = group_id
instance.save()
return HttpResponseRedirect(reverse('cal:home'))
return render(request, 'cal/form.html', {'form': form})
I am trying to use an UpdateView in Django to update two forms simultaneously. I have the get method working properly, but when I submit the update, the post method creates a new instance of the forms. How can I grab the forms from the get method and update them without creating a new instance? Everything is redirecting fine and I am getting no errors.
class MotionStudyInstanceUpdateView(UpdateView):
model = MotionStudyInstance
fields = ['height', 'weight', 'skin_type_score', 'fitzpatrick_skin_type']
template_name = 'data/motionstudyinstance_update.html'
success_url = reverse_lazy('data:motion-studies')
def get_context_data(self, **kwargs):
pass
def get(self, request, **kwargs):
pk = self.kwargs['pk']
item = MotionStudyInstance.objects.get(id=pk)
general_info = item.general_info
form = MotionStudyInstanceForm(
initial={'height': item.height, 'weight': item.weight, 'skin_type_score': item.skin_type_score,
'fitzpatrick_skin_type': item.fitzpatrick_skin_type})
form_two = GeneralInfoForm(initial={'case_report_form_number': general_info.case_report_form_number,
'form_type': general_info.form_type,
'study_start_date': general_info.study_start_date,
'signed_consent': general_info.signed_consent,
'gender': general_info.gender,
'miscellaneous_notes': general_info.miscellaneous_notes,
'adverse_events': general_info.adverse_events})
return render(request, self.template_name, {'form': form, 'foreign_form': form_two})
def post(self, request, **kwargs):
if request.method == 'POST':
form = MotionStudyInstanceForm(request.POST)
foreign_form = GeneralInfoForm(request.POST)
if form.is_valid() and foreign_form.is_valid():
general_info = foreign_form.save()
user_form = form.save(commit=False)
user_form.general_info = general_info
user_form.save()
return redirect('data:motion-studies')
else:
form = MotionStudyInstanceForm()
return render(request, self.template_name, {'form': form})
forms.py
class MotionStudyInstanceForm(forms.ModelForm):
class Meta:
model = MotionStudyInstance
exclude = ('general_info',)
widgets = {
'validation_status': forms.HiddenInput(),
'author': forms.HiddenInput(),
'sibling': forms.HiddenInput()
}
class GeneralInfoForm(forms.ModelForm):
class Meta:
model = GeneralInfo
fields = '__all__'
widgets = {
'form_type': forms.HiddenInput(),
}
Looks like you should be using the instance parameter on the forms.
So you can update it on the post, and display it on the get
something like this:
def post(self, request, **kwargs):
pk = self.kwargs['pk']
item = MotionStudyInstance.objects.get(id=pk)
if request.method == 'POST':
form = MotionStudyInstanceForm(request.POST, instance=item)
Very, very new to Django. I'm attempting to create a user registration process with custom form inputs. However, I have a few issues with the general persisting of new users to the database. This is what I have defined in my forms.py:
class UserRegistrationForm(UserCreationForm):
required_css_class = 'required'
email = forms.EmailField()
first_name = forms.CharField()
last_name = forms.CharField()
class Meta:
model = User
fields = ('username', 'email', 'first_name', 'last_name')
def __init__(self, *args, **kwargs):
super(UserRegistrationForm, self).__init__(*args, **kwargs)
self.fields['username'].widget = TextInput(attrs={'placeholder': 'Username'})
self.fields['username'].required = True
self.fields['username'].error_messages = {'required': 'Please enter your username'}
self.fields['email'].widget = EmailInput(attrs={'placeholder': 'Email'})
self.fields['email'].required = True
self.fields['email'].error_messages = {'required': 'Please enter your email'}
self.fields['first_name'].widget = TextInput(attrs={'placeholder': 'Forename'})
self.fields['first_name'].required = True
self.fields['first_name'].error_messages = {'required': 'Please enter your first_name'}
self.fields['last_name'].widget = TextInput(attrs={'placeholder': 'Surname'})
self.fields['last_name'].required = True
self.fields['last_name'].error_messages = {'required': 'Please enter your last_name'}
self.fields['password1'].widget = PasswordInput(attrs={'placeholder': 'Password'})
self.fields['password1'].required = True
self.fields['password1'].error_messages = {'required': 'Please enter your Password'}
self.fields['password2'].widget = PasswordInput(attrs={'placeholder': 'Confirm password'})
self.fields['password2'].required = True
self.fields['password2'].error_messages = {'required': 'Please confirm your Password'}
I also have the following in my views.py file:
class UserRegistrationView(FormView):
disallowed_url = ''
form_class = UserRegistrationForm
success_url = '/blog'
template_name = 'oauth/user/registration_form.html'
def registration_allowed(self):
return getattr(settings, 'REGISTRATION_OPEN', True)
def register(self, request):
if request.method == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
new_user = form.save(commit=False)
new_user.set_password(form.cleaned_data['password1'])
new_user.save()
return render(request, 'blog/post/list.html', { 'new_user': new_user })
else:
return render(request, 'oauth/user/registration_form.html', { 'form': form })
def get_success_url(self, user=None):
return super(UserRegistrationView, self).get_success_url()
What's worrying:
It doesn't create a new user in the database to be logged in (should I expect to be able to see the newly created user in the general admin dashboard?)
It is able to sign in with the superuser! (I really don't get that one bit, like, seriously couldn't write that if I tried!)
Any help in where I may have gone wrong would be much appreciated!
You're confusing class based and function based views. Class based views don't have a method called "register"; yours will never be called.
Actually the method you have written is a complete function based view in its own right. Extract it from the class, remove the self parameter, and change the urlconf to call the function directly, and it should work.
Create a post method (or change the name of register to post) and add it to your class so that as_view() knows what to do with your class. then add it to your urlconf as a view.
class UserRegistrationView(FormView):
disallowed_url = ''
form_class = UserRegistrationForm
success_url = '/blog'
template_name = 'oauth/user/registration_form.html'
def post(self, request, *arg, **kwargs):
return self.register(request)
def registration_allowed(self):
return getattr(settings, 'REGISTRATION_OPEN', True)
def register(self, request):
if request.method == 'POST':
form = UserRegistrationForm(request.POST)
if form.is_valid():
new_user = form.save(commit=False)
new_user.set_password(form.cleaned_data['password1'])
new_user.save()
return render(request, 'blog/post/list.html', {'new_user': new_user})
else:
return render(request, 'oauth/user/registration_form.html', {'form': form})
def get_success_url(self, user=None):
return super(UserRegistrationView, self).get_success_url()
Then, in your urls:
urlpatterns = [
# . . .
url(r'^register_user/$', UserRegistrationView.as_view(), name='register_user'),
# . . .
]
You could also add a get method and use it to render your form if you are interested in keeping all of your form logic contained to one view class.
My model.py
class VehicleInquiry(TimeStampedModel):
inquiry_status = models.PositiveSmallIntegerField(_("inquiry status"), choices=INQUIRY_STATUS_CHOICES, default=1)
full_name = models.CharField(_("full name"), max_length=100)
address = models.CharField(_("address"), max_length=200)
---- other fields ----
My Crispy form.py:
class VehicleInquiryForm(forms.ModelForm):
--- overridden form fields ---
class Meta:
model = VehicleInquiry
fields = ('full_name', 'email')
def __init__(self, request=None, stock_product=None, *args, **kwargs):
super(VehicleInquiryForm, self).__init__(*args, **kwargs)
self.fields['is_subscribed'].label = _("Keep me updated")
self.helper = FormHelper()
self.helper.template_pack = "bootstrap3"
self.helper.form_method = "post"
self.helper.form_id = "vehicle-shipping-form"
self.initial['insurance'] = True
self.helper.add_input(
Submit('inquiry', _('Inquiry'), css_class='btn btn-default',)
)
self.helper.form_method = 'post'
self.helper.layout = Layout(
Fieldset(
_("1. Choose Your Final Destination"),
Div(
Field('country2', css_class="order-select-country"),
),
--- other fields ---
)
My CBV view.py:
class VehicleStockDetailView(TemplateView):
model = StockProduct
template_name = "site/product/vehicle-detail.html"
template_name_done = "site/contact/contact-us-done.html"
template_name_done_email = "site/contact/emails/contact-us-done.html"
def post(self, request, slug, *args, **kwargs):
form = VehicleInquiryForm(request.POST)
ip=""
if form.is_valid():
form.save()
return render(request, self.template_name_done, {
'full_name': request.POST['full_name'],
'email': request.POST['email'],
})
vehicle = get_object_or_404(VehicleStock, slug=slug)
return render(request, self.template_name, {
'product': vehicle,
'form': form
})
def get(self, request, slug, *args, **kwargs):
vehicle = get_object_or_404(VehicleStock, slug=slug)
form = VehicleInquiryForm()
return render(request, self.template_name, {
'product': vehicle,
'form': form
})
The form is loading OK as a GET, but when I click Inquiry as a post request it does not show neither errors nor entered values. In my template I am loading form as {% crispy form %}. Need help.
You've changed the signature of the form init function so that the first positional arguments are request and stock_product. You should avoid doing this - generally I'd recommend using kwargs instead, but in this case you should remove them completely as you are not using those values in that method.
Also note your code would be simpler if you used a FormView, which takes care of almost all of that logic for you.
MY CreateView worked fine but when I try to update this form data, it gives blank form. No data was shown. Here is my views.py:
#require_authenticated_permission(
'member.add_person')
class PersonCreate( FormsetMixin, CreateView):
template_name = 'member/person_form.html'
model = Person
form_class = MemberForm
formset_class = PersonFormSet
#require_authenticated_permission(
'member.change_person')
class PersonUpdate( FormsetMixin, UpdateView):
template_name = 'member/person_form.html'
model = Person
form_class = MemberForm
formset_class = PersonFormSet
Here is my formset:
class MemberForm(ModelForm):
class Meta:
model = Person
exclude = ('user',)
def __init__(self, *args, **kwargs):
super(MemberForm, self).__init__(*args, **kwargs)
self.fields['name'].widget.attrs['placeholder'] = 'Your full name'
self.fields['tele_land'].label = 'Land phone'
self.fields['tele_cell'].label = 'Cell phone'
self.fields['passing_year'].label = 'Passing year'
self.fields['passing_year'].help_text = 'According to your session year'
def save(self, request, commit=True):
person = super().save(commit=False)
if not person.pk:
person.user = get_user(request)
if commit:
person.save()
self.save_m2m()
return person
class ChildrenForm(ModelForm):
class Meta:
model = Children
fields = '__all__'
PersonFormSet = inlineformset_factory(Person, Children, extra=0, min_num=1, fields=('child_name', 'child_birth_date','blood_group' ))
Url:
url(r'^person/create/$', views.PersonCreate.as_view(), name='person-create'),
url(r'^person/(?P<slug>[\w\-]+)/update/$', views.PersonUpdate.as_view(), name='person-update'),
My formset mixin:
class FormsetMixin(object):
object = None
def get(self, request, *args, **kwargs):
if getattr(self, 'is_update_view', False):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset_class = self.get_formset_class()
formset = self.get_formset(formset_class)
return self.render_to_response(self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
if getattr(self, 'is_update_view', False):
self.object = self.get_object()
form_class = self.get_form_class()
form = self.get_form(form_class)
formset_class = self.get_formset_class()
formset = self.get_formset(formset_class)
if form.is_valid() and formset.is_valid():
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
def get_formset_class(self):
return self.formset_class
def get_formset(self, formset_class):
return formset_class(**self.get_formset_kwargs())
def get_formset_kwargs(self):
kwargs = {
'instance': self.object
}
if self.request.method in ('POST', 'PUT'):
kwargs.update({
'data': self.request.POST,
'files': self.request.FILES,
})
return kwargs
def form_valid(self, form, formset):
self.object = form.save(self.request)
formset.instance = self.object
formset.save()
return redirect(self.object.get_absolute_url())
def form_invalid(self, form, formset):
return self.render_to_response(self.get_context_data(form=form, formset=formset))
Why my UpdateView gives blank form?
Proably because getattr(self, 'is_update_view', False) always is false so you're not loading any self.object.
I have for similar situations used ModelFormSetView from django-extra-views. It's easy to use if you already understand Django class based views and will save you a lot of code to write and maintain yourself.
BTW you should implement get_queryset and prefetch the Children.
Use in UpdateView: is_update_view = True. It will be like
class PersonUpdate( FormsetMixin, UpdateView):
template_name = 'member/person_form.html'
model = Person
is_update_view = True
form_class = MemberForm
formset_class = PersonFormSet