How do I get my Django generic view to work - django

Am I going about this the right way? Having never used 'generic views' I am tying to use Django's generic.UpdateView view. When I 'hit' the 'update' button on the form, I get an invalid form response with message 'Library with this Slide name already exists'
Grateful for any help.
View:
class Slideview(generic.UpdateView):
model = Library
template_name = 'app1/slide_update.html'
fields = ['slide_name', 'reference_value','esd',
'current_mean', 'counts_averaged', 'status']
context_object_name = 'qc_slide'
#def get_queryset(self):
#slide_id = self.kwargs['pk']
#return Library.objects.filter(slide_name=slide_id)
def get_success_url(self):
return reverse('Slideview', args=[self.kwargs['pk']])
def get_context_data(self, **kwargs):
context = super(Slideview, self).get_context_data(**kwargs)
#form = self.get_form(self.get_form_class())
#context['form'] = form
return context
def post(self, request, *args, **kwargs):
print("Im in post")
form = self.get_form(self.get_form_class())
if form.is_valid():
#Code will go here which will query a second model
#perform a series of math calculations and then
#return the updated information
self.object = self.get_object()
self.object.save()
return self.form_valid(form)
else:
print("Form not valid")
self.object = self.get_object()
return self.form_invalid(form)
Model:
class Library(models.Model):
slide_name = models.CharField(max_length=5, primary_key=True)
reference_value = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
esd = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
current_mean = models.DecimalField(max_digits=5, decimal_places=2, default=Decimal(0))
counts_averaged = models.IntegerField(default=0)
status = models.CharField(max_length=9)
def __str__(self):
return self.slide_name

Related

MultiValueDictKeyError hen adding lines with inlineformset

I am trying to add more lines on the inline formset factory using the same methodology that I used before on a formset factory but is getting an error:
MultiValueDictKeyError form-TOTAL_FORMS'
models.py:
class ttransactions(models.Model):
transaction_type = models.CharField(max_length=10, choices=tx_choices)
description = models.CharField(max_length=50, null=False, blank=False, default='Description')
transaction_date = models.DateField(default=datetime.today, db_index=True)
company = models.ForeignKey(tcompany, on_delete=models.PROTECT, db_index=True)
def __str__(self):
return self.description
class ttransaction_lines(models.Model):
transaction = models.ForeignKey(ttransactions, on_delete=models.PROTECT, db_index=True)
sequence = models.IntegerField()
transaction_type = models.CharField(max_length=6, choices=debit_credit)
ledger_account = models.ForeignKey(tledger_account, on_delete=models.PROTECT, db_index=True)
amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.0)
vat_amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.0)
vat_code = models.ForeignKey(tvat, on_delete=models.PROTECT, blank=True, null=True)
quantity = models.IntegerField(blank=True, null=True)
posted = models.BooleanField(default=True)
forms.py:
class TransactionsForm(forms.ModelForm):
transaction_date = forms.DateField(widget=forms.SelectDateWidget(years=year_range), initial=datetime.today)
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(TransactionsForm, self).__init__(*args, **kwargs)
class Meta:
model = ttransactions
fields = ['description',
'transaction_date']
class TransactionLinesForm(forms.ModelForm):
class Meta:
model = ttransaction_lines
fields = ['transaction_type', 'ledger_account', 'amount']
class BaseTransactionLinesFormSet(BaseModelFormSet):
def clean(self):
super(BaseTransactionLinesFormSet, self).clean()
# Check errors dictionary first, if there are any error, no point in validating further
if any(self.errors):
return
balance = 0
for form in self.forms:
if form.cleaned_data['DELETE'] == True or form.cleaned_data['DELETE'] == '':
continue
if form.cleaned_data['transaction_type']=='Debit':
balance = balance + form.cleaned_data['amount']
else:
balance = balance - form.cleaned_data['amount']
if balance != 0:
message = 'Transactions not balanced (excluding deleting lines)'
raise forms.ValidationError(message)
TransactionLineFormset = inlineformset_factory(ttransactions,
ttransaction_lines,
form=TransactionLinesForm,
can_order=True, can_delete=True)
views.py
class JournalCreateView(LoginRequiredMixin, CreateView):
template_name = 'accounting/journal.html'
model = ttransactions
formset = TransactionLineFormset
form_class = TransactionsForm
success_url = '/accounting/transaction_list'
def get_form_kwargs(self):
kwargs = super(JournalCreateView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = TransactionLineFormset(queryset=ttransaction_lines.objects.none())
formset.form.base_fields['ledger_account'].queryset = \
tledger_account.objects.filter(company=request.user.current_company)
return self.render_to_response(
self.get_context_data(form=form, formset=formset))
def post(self, request, *args, **kwargs):
extra_forms = 1
if 'additems' in request.POST and request.POST['additems'] == 'true':
formset_dictionary_copy = self.request.POST.copy()
formset_dictionary_copy['form-TOTAL_FORMS'] = \
int(formset_dictionary_copy['form-TOTAL_FORMS']) + extra_forms
formset = TransactionLineFormSet(formset_dictionary_copy)
return self.render_to_response(
self.get_context_data(form=form,
formset=formset))
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
formset = TransactionLineFormset(self.request.POST)
if (form.is_valid() and formset.is_valid()):
return self.form_valid(form, formset)
else:
return self.form_invalid(form, formset)
def form_valid(self, form, formset):
form.instance.company = self.request.user.current_company
self.object = form.save()
sequence = 1
for line in formset:
line.instance.sequence = sequence
sequence += 1
formset.instance = self.object
formset.save()
return super().form_valid(form)
def form_invalid(self, form, formset):
return self.render_to_response(
self.get_context_data(form=form,
formset=formset))
I get the error on the line that dictionary copy line. The code for adding the line is added in the post function. I am not sure if that is the correct place to add this code. Help will be appreciated.
This error occurs if the form submitted is missing the key.
The .get() method allows for a default when accessing a missing key in a dict.
formset_dictionary_copy['form-TOTAL_FORMS'] = \
int(formset_dictionary_copy.get('form-TOTAL_FORMS', 1)) + extra_forms
It appears that, the formset is not getting all it's hidden values or a different form is submitted.
I would consider a frontend solution or sending a single initial formset with the add button values.
A frontend / js example:
https://www.brennantymrak.com/articles/django-dynamic-formsets-javascript

Django key error "request" when using inlineformset

I created a Class Based(CreateView) with in inlineformset. I need to pass the request.user to the form to enable a filter function on one of the form fields. I however get a Key Error: request on the line:
self.request = kwargs.pop('request') in the def __init__(self, *args, **kwargs): of the form. Assistance will be appreciated.
Tips on my programming also welcome.
models.py:
class ttransactions(models.Model):
transaction_type = models.CharField(max_length=10, choices=tx_choices)
description = models.CharField(max_length=50, null=False, blank=False, default='Description')
transaction_date = models.DateField(default=datetime.today, db_index=True)
company = models.ForeignKey(tcompany, on_delete=models.PROTECT, db_index=True)
def __str__(self):
return self.description
class ttransaction_lines(models.Model):
transaction = models.ForeignKey(ttransactions, on_delete=models.CASCADE, db_index=True)
sequence = models.IntegerField()
transaction_type = models.CharField(max_length=6, choices=debit_credit)
ledger_account = models.ForeignKey(tledger_account, on_delete=models.PROTECT, db_index=True)
amount = models.DecimalField(max_digits=14, decimal_places=2, default=0.0)
quantity = models.IntegerField(blank=True, null=True)
posted = models.BooleanField(default=True)
forms.py:
class TransactionLinesForm(forms.ModelForm):
class Meta:
model = ttransaction_lines
fields = ['transaction_type', 'ledger_account', 'amount']
def __init__(self, *args, **kwargs):
self.request = kwargs.pop('request')
super(TransactionLinesForm, self).__init__(*args, **kwargs)
user = self.request.user
current_company = user.current_company
self.fields['ledger_account'].queryset = tledger_account.objects.filter(
company=current_company)
TransactionLineFormset = inlineformset_factory(ttransactions,
ttransaction_lines,
# fields=['transaction_type', 'ledger_account', 'amount'] ,
form=TransactionLinesForm,
can_order=True, can_delete=True)
views.py:
class JournalCreateView(LoginRequiredMixin, CreateView):
template_name = 'accounting/journal.html'
model = ttransactions
transaction_lines_form = TransactionLineFormset
form_class = TransactionsForm
success_url = '/accounting/transaction_list'
def get_form_kwargs(self):
kwargs = super(JournalCreateView, self).get_form_kwargs()
kwargs['request'] = self.request
return kwargs
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
transaction_lines_form = TransactionLineFormset()
return self.render_to_response(
self.get_context_data(form=form, transaction_lines_form=transaction_lines_form))
def post(self, request, *args, **kwargs):
extra_forms = 1
if 'additems' in request.POST and request.POST['additems'] == 'true':
formset_dictionary_copy = self.request.POST.copy()
formset_dictionary_copy['form-TOTAL_FORMS'] = \
int(formset_dictionary_copy['form-TOTAL_FORMS']) + extra_forms
transaction_lines_form = TransactionLinesFormSet(formset_dictionary_copy)
return self.render_to_response(
self.get_context_data(form=form,
transaction_lines_form=transaction_lines_form))
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
transaction_lines_form = TransactionLineFormset(self.request.POST)
if (form.is_valid() and transaction_lines_form.is_valid()):
return self.form_valid(form, transaction_lines_form)
else:
return self.form_invalid(form, transaction_lines_form)
def form_valid(self, form, transaction_lines_form):
form.instance.company = self.request.user.current_company
self.object = form.save()
sequence = 1
for line in transaction_lines_form:
line.instance.sequence = sequence
sequence += 1
transaction_lines_form.instance = self.object
transaction_lines_form.save()
return super().form_valid(form)
def form_invalid(self, form, transaction_lines_form):
return self.render_to_response(
self.get_context_data(form=form,
transaction_lines_form=transaction_lines_form))
Your form may be initialized at multiple places. It is difficult to find where the error happened without seeing the Traceback.
So, it is better to keep the filtering logic in the view rather than passing 'request' to form. Remove __init__ method in form and try the below code in the view
def get(self, request, *args, **kwargs):
self.object = None
form_class = self.get_form_class()
form = self.get_form(form_class)
transaction_lines_form = TransactionLineFormset()
transaction_lines_form.form.base_fields['ledger_account'].queryset = \
tledger_account.objects.filter(company=request.user.current_company)
return self.render_to_response(
self.get_context_data(form=form, transaction_lines_form=transaction_lines_form))

update a Model field in DetailView

As a newbie in Django, I'm sure there is something obvious I'm not seeing. I have a user model with a one to one relationship to a userprofile model, where I'm storing the profile photo. I mixed DetailView and Formview because I want the user to go to his details page and update just the photo, but somehow its not working for me. I know I could do the job with UpdateView, but for didactic purposes, can anyone tell me why this is not working? I'm trying to updated the model fields in the form_valid method but this is not working, they just remain with the old values. I thought at the beginning it was the photo that could not be updated because of some errors on my side, but I've tried also updating other string fields and it doesnt work. Here the code: (the commented out fields are the places where I tried updating several model fields using get_object_or_404 and other functions)
class UserDetail(FormMixin, DetailView):
template_name = "users/user_detail.html"
model = User
form_class = forms.UserPhotoForm
def get_success_url(self):
return reverse('users:user_detail', args=[str(self.get_object().pk)])
def get_context_data(self, **kwargs):
user = self.get_object()
form = forms.UserPhotoForm(instance=user)
context = super().get_context_data(**kwargs)
context['user_rating'] = CotizacionReview.objects.filter(cotizacion__user=self.get_object()).aggregate(Avg('nota'))
context['form'] = form
return context
def form_valid(self, form):
form.save()
return super(UserDetail, self).form_valid(form)
def post(self, request, *args, **kwargs):
a = get_object_or_404(User, pk=self.get_object().id).userprofile
form = forms.UserPhotoForm(request.FILES['avatar'], instance=a)
# get_object_or_404(User, pk=self.get_object().id).apellido = '1234'
if form.is_valid():
# print(get_object_or_404(User, pk=self.get_object().id).userprofile.avatar)
# I tried updating several model fields here, but didnt work
# print(request.FILES['avatar'])
return self.form_valid(form)
else:
return self.form_invalid(form)
Here the model:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
avatar = models.ImageField(upload_to='profile_pics', default='profile_pics/default-user-icon-4.jpg', blank=True)
telefono = models.CharField(max_length=12, blank=True)
nombre = models.CharField(max_length=64, blank=True)
apellido = models.CharField(max_length=64, blank=True)
link = models.CharField(max_length=256, blank=True)
educacion = models.CharField(max_length=256, blank=True)
experiencia = models.TextField(max_length=512, blank=True)
birthdate = models.DateField(blank=True, null=True)
#receiver(post_save, sender=User)
def update_profile_signal(sender, instance, created, **kwargs):
if created:
UserProfile.objects.create(user=instance)
So I achieved it by using commit=False in my form_valid method:
class UserDetail(FormMixin, DetailView):
template_name = "users/user_detail.html"
model = User
form_class = forms.UserPhotoForm
def get_success_url(self):
return reverse('users:user_detail', args=[str(self.get_object().pk)])
def get_context_data(self, **kwargs):
user = self.get_object()
form = forms.UserPhotoForm(instance=user)
context = super().get_context_data(**kwargs)
context['user_rating'] = CotizacionReview.objects.filter(cotizacion__user=self.get_object()).aggregate(Avg('nota'))
context['form'] = form
return context
def form_valid(self, form):
user_instance = form.save(commit=False)
user_instance.avatar = form.cleaned_data['avatar']
user_instance.id = self.get_object().userprofile.id
user_instance.save(update_fields=['avatar'])
return super(UserDetail, self).form_valid(form)
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
return self.form_valid(form)
else:
return self.form_invalid(form)
But now I have another problem. Every time I update the photo, a new photo is saved to the database. Is there a way of doing this and deleting the old photo? or replacing it?

UNIQUE constraint failed: auth_user.username while updating user info

I am trying to give a user the option to change his/her first/last name through a ModelForm. When I press submit, I get hit with the UNIQUE constraint failed: auth_user.username error. Here are my codes:
students/forms.py:
class EditProfileForm(UserChangeForm):
def clean_password(self):
# Overriding the default method because I dont want user to change
# password
pass
class Meta:
model = User
fields = (
'first_name',
'last_name',
)
students/views.py:
User = get_user_model()
def student_profile_view(request, slug):
if request.method == 'GET':
# forms
edit_name_form = EditProfileForm(instance=request.user)
context = {
'edit_name_form': edit_name_form,
}
return render(request, "students/profile.html", context)
class ChangeNameView(SuccessMessageMixin, UpdateView):
template_name = 'students/edit_profile.html'
model = User
form_class = EditProfileForm
success_message = "Your name has been updated"
def post(self, request, *args, **kwargs):
form = self.get_form()
if form.is_valid():
form.instance.student_profile = StudentProfile.objects.get(slug=request.user.student_profile.slug)
return self.form_valid(form)
else:
return self.form_invalid(form)
def form_valid(self, form):
"""If the form is valid, save the associated model."""
form.instance.username = self.request.user
self.object = form.save(commit=False)
return super().form_valid(form)
def get_success_url(self):
return reverse('students:student_profile_view', kwargs={'slug': self.object.student_profile.slug})
also fyi, User model is foreign key with StudentProfile.
students/models.py:
class StudentProfile(models.Model):
user = models.OneToOneField(User, related_name='student_profile', on_delete=models.CASCADE)
slug = models.SlugField(blank=True, unique=True)
avatar = models.ImageField(upload_to='student_profile/', null=True, blank=True)
description = models.CharField(max_length=120, null=True, blank=True)
objects = models.Manager()
def __str__(self):
return self.user.username
def get_absolute_url(self):
return reverse("students:student_profile_view", kwargs={"slug": self.slug})
I am pretty new to class based view so maybe I'm doing something wrong there?
I assume you do not have the user within the form so you need the form
def get_context_data (self, *args, **kwargs)
ctx = super().get_context_data(*args, **kwargs)
if self.request.method == 'POST':
ctx['form'] = EditProfileForm(instance=self.request.user)
and remove def form_valid()

Django UpdateView with related model

I have the following situation and I don't know how to do an update:
#models.py
class Task(models.Model):
creation_date = models.DateField(
default=None,
)
name = models.CharField(
max_length=255,
)
description = models.TextField(
max_length=500,
blank=True,
null=True,
)
class TaskDetails(models.Model):
PEND = 1
COMP = 2
TASK_STATUS = (
(PEND, 'pending'),
(COMP, 'completed'),
)
task = models.OneToOneField(
Task,
primary_key=True,
on_delete=models.CASCADE
)
solution = models.CharField(
max_length=255,
)
due_date = models.DateField(
null=True,
default=None,
blank=True,
)
status = models.PositiveSmallIntegerField(
default=1,
choices=TASK_STATUS,
)
And now my view
#views.py
class TaskUpdate(UpdateView):
model = Task
second_model = TaskDetails
form_class = TaskForm
second_form_class = TaskDetailsForm
pk_url_kwarg = 'task_id'
def get_context_data(self, **kwargs):
context = super(TaskUpdate, self).get_context_data(**kwargs)
if self.request.method == 'POST':
details_form = self.second_form_class(self.request.POST, prefix='details')
else:
details_object = self.second_model.objects.get(pk=self.kwargs.get(self.pk_url_kwarg))
details_form = self.second_form_class(instance=details_object, prefix='details')
context['details_form'] = details_form
return context
def post(self, request, *args, **kwargs):
self.object = self.get_object()
form = self.form_class(request.POST)
details_form = self.second_form_class(request.POST, prefix='details')
if form.is_valid() and details_form.is_valid():
return self.form_valid(form, details_form)
else:
return self.form_invalid(form, details_form)
def form_valid(self, form, details_form):
form.instance.creation_date = datetime.now().date()
self.object = form.save()
details_form.instance.task = self.object
details_form.save()
return HttpResponseRedirect(self.success_url)
def form_invalid(self, form, details_form):
return self.render_to_response(self.get_context_data(form=form, details_form=details_form))
I also have the ModelForms: TaskForm and TaskDetailsForm. Not relevant here.
The 2 forms are displayed and submitted at once.
But instead of updating the existing record in Task and TaskDetails tables, it creates a new one in both tables
I think my problem is in form_valid. What should I put there?
Thanks a lot
In the post method forms are created without instance. You have to pass you instances there.
def post(self, request, *args, **kwargs):
# get current task
obj = self.get_object()
#initiate the task form with this object as instance
form = self.form_class(request.POST, instance=obj)
#get realted details object or None.
#I can't check if this string works, but it should.
details_obj = getattr(object, 'taskdetails', None)
#initiate the details_form with this details_object as instance
details_form = self.second_form_class(request.POST, prefix='details',
instance=details_obj)
if form.is_valid() and details_form.is_valid():
return self.form_valid(form, details_form)
else:
return self.form_invalid(form, details_form)
def form_valid(self, form, details_form):
#save object
obj = form.save(commit=False)
obj.creation_date = datetime.now().date()
obj.save()
#save details_object
details_obj = details_form.save(commit=False)
details_obj.task = obj
details_obj.save()
return HttpResponseRedirect(self.success_url)
I think this should work. You don't need the form_valid and form_invalid methods in this case,
def post(self, request, *args, **kwargs):
response = super(TaskUpdate, self).post(request, *args, **kwargs)
details_form = self.second_form_class(self.request.POST, prefix='details')
if details_form.is_valid():
task = self.get_object()
self.second_model.objects.filter(task=task)
.update(**details_form.cleaned_data)
return response
return render(request, self.template_name, {
'form': self.get_form(self.get_form_class()),
'details_form': details_form,
})
PS: put related_name=task_details for the OneToOneField and auto_now_add=True for the creation date of your task
Nevertheless, why don't you include the Task Details into task and stop using this OneToOneKey?