Django formset: file upload does not work - django

I am using Django to build a help desk and I want to allow the client to upload multiple files when they submit a ticket.
I am trying to do this by using a formset.
I have found many questions related to a similar problem, but I still have not been able to get my form working. I will appreciate a pointer in the right direction.
I have posted the relevant code below:
# models.py
class Ticket(models.Model):
PRIORITY_CHOICES = (
(1, 'Critical'),
(2, 'High'),
(3, 'Normal'),
(4, 'Low'),
(5, 'Very Low'),
)
STATUS_CHOICES = (
(1, 'Open'),
(2, 'Reopened'),
(3, 'Resolved'),
(4, 'Closed'),
(5, 'Duplicate'),
)
ticket_number = models.CharField(max_length=50, blank=True,
null=True, unique=True)
client = models.ForeignKey(settings.AUTH_USER_MODEL,
editable=True, on_delete=models.CASCADE,
related_name="tickets")
title = models.CharField("Summary",
max_length=200,
help_text='Provide a brief description of your request.')
description = models.TextField(blank=True,
help_text='Provide as much detail as possible to help us resolve this ticket as quickly as possible.')
due_date = models.DateField(blank=True, null=True)
assigned_to = models.ForeignKey(settings.AUTH_USER_MODEL,
related_name="assigned",
blank=True, null=True,
on_delete=models.CASCADE)
priority = models.IntegerField(choices=PRIORITY_CHOICES,
editable=True, default=3,
help_text='Please select a priority carefully. If unsure, leave it as "Normal".',
blank=True, null=True)
status = models.IntegerField(choices=STATUS_CHOICES,
editable=True,
default=1, blank=True, null=True)
closing_date = models.DateField(blank=True, null=True)
closing_notes = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
upload = models.FileField(upload_to='uploads/%Y/%m/%d/', blank=True, null=True)
def get_absolute_url(self):
return reverse('tickets:ticket-detail', args=[str(self.id)])
def __str__(self):
return str(self.ticket_number)
class TicketFile(models.Model):
attachment = models.FileField(upload_to='attachments/%Y/%m/%d', blank=True, null=True)
ticket = models.ForeignKey(Ticket, editable=True, on_delete=models.CASCADE)
description = models.CharField(max_length=200, blank=True)
def get_image_filename(self):
if self.docfile:
return media_url + str(self.docfile)
def __str__(self):
return str(self.attachment)
# forms.py
class TicketFormIsStaff(ModelForm):
class Meta:
model = Ticket
exclude = ()
class TicketFileForm(forms.ModelForm):
attachment = forms.FileField(label='Attachment')
class Meta:
model = TicketFile
fields = ('attachment', )
TicketAttachmentFormset = modelformset_factory(Ticket,
form=TicketFileForm, extra=2)
# views.py
class TicketCreateView(CreateView):
model = Ticket
fields = ['ticket_number', 'client', 'title', 'description', 'status']
def get_context_data(self, **kwargs):
data = super(TicketCreateView, self).get_context_data(**kwargs)
if self.request.POST:
data['ticketfiles'] = TicketAttachmentFormset(self.request.POST)
print("data 1 ", data['ticketfiles'] )
else:
data['ticketfiles'] = TicketAttachmentFormset()
print("data 2 ", data)
return data
def form_valid(self, form):
context = self.get_context_data()
ticketfiles = context['ticketfiles']
with transaction.atomic():
self.object = form.save()
print("CONTEXT:::: ", ticketfiles)
if ticketfiles.is_valid():
ticketfiles.instance = self.object
ticketfiles.save()
return super(TicketCreateView, self).form_valid(form)
# ticket_form.html
<form action="" method="post" enctype="multipart/form-data">{% csrf_token %}
{{ form.as_p }}
<table class="table">
{{ ticketfiles.management_form }}
{% for form in ticketfiles.forms %}
{% if forloop.first %}
<thead>
<tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr>
</thead>
{% endif %}
<tr class="{% cycle row1 row2 %} formset_row">
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
<input type="submit" value="Save" />
</form>
All the data related to the Ticket model uploads correctly, but the files related to the TicketFile model do not upload. If I try to upload the data in the admin interface then everything works perfectly. I assume this means that all the media settings are correct.
If I add the description field to the formset then that gets saved correctly. The file is not saved though.

I resolved the file upload problem by changing the code in TicketCreateView from:
data['ticketfiles'] = TicketAttachmentFormset(self.request.POST)
to:
data['ticketfiles'] = TicketAttachmentFormset(self.request.POST, self.request.FILES, instance=form.instance))

Related

How to query from 2 Models in Class based views in django?

I have a model Log and another model Solutions and I am using DetailView to display details of each log
Each log can have many solutions.
There is a log field in the Solutions model that is Foreign Key to Log model..
Now how do I access both Log model and Solutions of that particular log in the same html template if I want to display all the solutions of that particular log below the details of the log
models.py:
class Log(models.Model):
title = models.CharField(blank=False, max_length=500)
content = models.TextField(blank=False)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
slug = models.SlugField(max_length=50, null=False, unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE,null=True, blank=True)
image = models.ImageField(
upload_to='images', blank=True)
def save(self, *args, **kwargs):
super().save()
self.slug = self.slug or slugify(self.title + '-' + str(self.id))
super().save(*args, **kwargs)
class Meta:
verbose_name = ("Log")
verbose_name_plural = ("Logs")
def __str__(self):
return f"{self.title}"
def get_absolute_url(self):
return reverse("log-detail", kwargs={"question": self.slug})
class Solutions(models.Model):
log = models.ForeignKey(
Log, on_delete=models.CASCADE, blank=True, null=True)
author = models.ForeignKey(User, on_delete=models.CASCADE,null=True, blank=True)
solution = models.TextField(null=True)
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
slug = models.SlugField(max_length=50, null=False, blank=True)
image = models.ImageField(
upload_to='images', blank=True)
def save(self, *args, **kwargs):
self.slug = self.slug or slugify(self.solution)
super().save(*args, **kwargs)
class Meta:
verbose_name = ("Solution")
verbose_name_plural = ("Solutions")
def __str__(self):
return f" {self.solution} "
views.py:
class LogDetailView(DetailView):
model = Log
slug_url_kwarg = 'question'
slug_field = 'slug'
log_detail.html:
{% extends 'log/base.html' %}
{%load crispy_forms_tags %}
{% block content %}
<title>Error Logger - {{object.title}}</title>
<div class="main container mt-4 p-3 mb-4">
<img style='display:inline;' class='rounded-circle account-img' src="{{ object.author.profile.avatar.url }}" alt="">
<h1 style='display:inline;'>
{{ object.title }}
</h1>
<p>Author: {{ object.author }}</p>
<p>Date and time of creation: {{ object.created }}</p>
<span> Details </span>:
<p class="big ml-4">{{ object.content }} <br />
{% if object.image %}
<img style="width: 20vw" class="mt-4" src="{{ object.image.url }}" alt="image" />
{% else %}
{% endif %}
</p>
</div>
<br />
<a
class="btn btn-outline btn-info button-solution"
href="#"
>Add solution</a
>
You can enumerate over these in the template by accessing the relation in reverse, this is normally modelname_set, unless you set a related_name=…. So in this case it is solutions_set:
{% for solution in object.solutions_set.all %}
{{ solution }}
{% endfor %}
If the ForeignKey has a related_name=… [Django-doc], for example with:
class Solutions(models.Model):
log = models.ForeignKey(
Log,
on_delete=models.CASCADE,
blank=True,
null=True,
related_name='solutions'
)
# …
Then we access this with:
{% for solution in object.solutions.all %}
{{ solution }}
{% endfor %}
Note: normally a Django model is given a singular name, so Solution instead of Solutions.

In Django getting errors based on dropdown selection

Getting error when fields are selected from the down. Not seeing why it is throwing error
Django Dropdown form.error: ERROR ALERT:
location_name Select a valid choice. That choice is not one of the available choices.
Here is the model, form, view and html looks like
MODEL
class Code (models.Model):
name = models.CharField(max_length=4, default=None, blank=True)
def __str__(self): return self.name
class Device (models.Model):
code = models.ForeignKey(Code, on_delete=models.CASCADE, null=True)
ip = models.GenericIPAddressField(protocol='IPv4', unique=True)
def __str__(self): return self.ip
class SiteData (models.Model):
site = models.ForeignKey(Code, on_delete=models.SET_NULL, null=True)
site_ip = models.ForeignKey(Device, on_delete=models.SET_NULL, null=True)
site_data1 = models.CharField(max_length=3, default='120')
class CombineData(models.Model):
location = models.ForeignKey(Code, on_delete=models.SET_NULL, null=True)
device = models.ForeignKey(AddData, on_delete=models.SET_NULL, null=True)
locdata = models.ForeignKey(SiteData, on_delete=models.SET_NULL, null=True)
FORM
class CombineData_form(forms.ModelForm):
class Meta:
model = P2_NexusLeafPair
fields = '__all__'
VIEW
def comboView(request, *args, **kwargs):
template_name = 'site_display.html'
code = Code.objects.order_by('-name')
device = Device.objects.order_by('-ip')
sitename = SiteData.objects.order_by('-site')
content = {
'code': code,
'device': device,
'sitename': sitename
}
if request.method == 'POST':
form = CombineData_form(request.POST or None)
print(form.is_valid())
#print(form.errors)
if form.is_valid():
. . .
else:
messages.error(request, form.errors)
else:
form = CombineData_form()
return render(request, template_name, content)
HTML
<form id="comboView" class="post-form" role=form method="POST" action="comboView">{% csrf_token %}
<div name="code" class="dropdown" id="mainselection" required>
<select name="dc_name">
<option class="dropdown-item" value="">---------</option>
{% for item in code %}
<option value="{{ item }}">{{ item }}</option>
{% endfor %}
</div>
<input type="submit" value="Submit" />
</form>
{same as other fields: device, sitename}
{% for item in content %}
{{item.code}}
{% endfor %}
Try that.

Filter a queryset in Django based on a changing value

I build a Blogpost App with Django and want comments under the blogpost. I can already post new comments, and see comments, but I see every comment under every blogpost. .
class blogpost(models.Model):
user = models.ForeignKey(User, default=1, null=True, on_delete=models.SET_NULL)
title = models.TextField()
slug = models.SlugField(unique=True)
content = models.TextField(null=True, blank=True)
class blogcommment(models.Model):
user = models.ForeignKey(User, default=1, null=True, on_delete=models.SET_NULL)
post = models.ForeignKey(blogpost, default=1, null=True, on_delete=models.SET_NULL)
title = models.TextField()
content = models.TextField(null=True, blank=True)
def blogpost_detail_view (request, slug):
# Blogeintrag anzeigen
obj = blogpost.objects.get(slug=slug)
form = blogcommentform(request.POST or None)
qs = blogcommment.objects.filter(***What should stay here and why?***)
if form.is_valid():
comment = blogcommment.objects.create(**form.cleaned_data)
form = blogcommentform
template_name = 'blogpost_detail.html'
context = {"object": obj,'form': form,'object_list': qs}
return render(request, template_name, context)
{% extends "base.html" %}
{% block content %}
<h1>{{ object.title }}</h1>
<p>{{ object.content }}</p>
<form method="POST" action=""> {% csrf_token %}
{{ form.as_p }}
<button type='submit'>Senden</button>
</form>
{% for object in object_list %}
{{ object.content }}
{% endfor %}
{% endblock %}
class blogcommentform(forms.Form):
title = forms.CharField()
content = forms.CharField(widget=forms.Textarea)
Try this
qs = blogcommment.objects.filter(post_id=obj.id)
In blogcommment model you have a reference to blogpost. So, you can easily filter comment for which blog post related to it.
qs = blogcommment.objects.filter(post=obj)
This will tell the queryset to filter all blogcomments that have the current specific blogpost as their post reference.

pre-populating partial Initial data in a Django formset

I’m having difficulty implementing initial data in my django formset.
For context, I’m building an attendance app where a list of students exist and need to be assessed for attendance every day.
What I’m trying to do is have an administrator click on a link which has a date listed on it. They will then be taken to data grid where each row represents the number of students in the system along with 4 columns (student name, date, present/absent drop down, a notes field). The goal is to have the student name field be pre-populated with the the list of students in the Student model, the date field be pre-populated with the date on the link the user clicked and the attendance and notes fields be user inputs.
Any help would be much appreciated
Thanks!
—
Student model
class Student(models.Model):
first_name = models.CharField(max_length=255)
last_name = models.CharField(max_length=255)
GENDER_CHOICES = (
('male', 'male'),
('female', 'female'),
)
gender = models.CharField(max_length=12, choices=GENDER_CHOICES, null=False, blank=False)
date_of_birth = models.DateField(auto_now=False, auto_now_add=False)
#property
def full_name(self):
return ''.join([self.first_name, '_', self.last_name])
def __unicode__(self):
return self.full_name
Attendance model
class Attendance(models.Model):
student = models.ForeignKey('students.Student')
attendance_date = models.DateField(auto_now=False, auto_now_add=False)
STATUS_CHOICES = (
('present', ‘1’),
('absent', ‘0’),
)
status = models.CharField(max_length=12, choices=STATUS_CHOICES, null=False, blank=False)
notes = models.CharField(max_length=300, null=True, blank=True)
class Meta:
unique_together = ("student", "attendance_date")
def __unicode__(self):
return self.student
Attendance form
class AttendanceForm(forms.ModelForm):
class Meta:
model = Attendance
fields = ["student","attendance_date", "status", "notes"]
View
def add_attendance(request):
s = Student.objects.all().values()
AttendanceFormSet = formset_factory(AttendanceForm, extra=0)
formset = AttendanceFormSet(request.POST or None, initial=s)
if formset.is_valid():
try:
instance = form.save(commit=False)
instance.save()
return HttpResponseRedirect('/')
except:
return HttpResponseRedirect('/')
context = {
"formset": formset,
}
return render(request, "group_attendance.html", context)
Template
<table id="formset" class="form">
{{ formset.management_form }}
{% for form in formset.forms %}
{% if forloop.first %}
<thead><tr>
{% for field in form.visible_fields %}
<th>{{ field.label|capfirst }}</th>
{% endfor %}
</tr></thead>
{% endif %}
<tr>
{% for field in form.visible_fields %}
<td>
{# Include the hidden fields in the form #}
{% if forloop.first %}
{% for hidden in form.hidden_fields %}
{{ hidden }}
{% endfor %}
{% endif %}
{{ field.errors.as_ul }}
{{ field }}
</td>
{% endfor %}
</tr>
{% endfor %}
</table>
I'm not really sure, if I understood your question the intended way. I think you're looking for a way to generate dynamic forms, which were pre-filled with data from the db.
A really helpful article about that, can be found here https://jacobian.org/writing/dynamic-form-generation/
You don't have a fixed numbers of students (-> fields in form), so you have to generate as many fields as needed. So you have to iterate over the students and create a form field for every single one.
You can find this in the article above. Just down below is a code snippet and an explanation of it:
class UserCreationForm(forms.Form):
username = forms.CharField(max_length=30)
password1 = forms.CharField(widget=forms.PasswordInput)
password2 = forms.CharField(widget=forms.PasswordInput)
def __init__(self, *args, **kwargs):
extra = kwargs.pop('extra')
super(UserCreationForm, self).__init__(*args, **kwargs)
for i, question in enumerate(extra):
self.fields['custom_%s' % i] = forms.CharField(label=question)

Django modelform not saving input choices and not returning errors

I have a modelform that only works(saves input data to database) if none of the fields has choices. When i introduce choices, i don't get any errors and the form seems to be valid but nothing gets saved.
I have combed through the documentation and i am not returning anything useful.
I am convinced that i need to do more in my views to get the selected input choices or i need to add a few methods to the model class. Please point me in the right direction.
Here is my model:
class OpeningHours(models.Model):
'''
'''
class Meta:
verbose_name = 'Opening Hour'
verbose_name_plural = 'Opening Hours'
#######################################################
mytime = Bizhours()
################################################
id = models.AutoField(primary_key=True)
company =models.CharField(max_length=100, null=True, blank=True)
weekday = models.CharField(max_length=100, choices=mytime.getweekdays(), default='Monday', null=True)
fromHour = models.CharField(max_length=100, null=True, blank=True)
fromMinute = models.CharField(max_length=100, null=True, blank=True)
toHour = models.CharField(max_length=100, null=True, blank=True)
toMinute = models.CharField(max_length=100, null=True, blank=True)
'''
id = models.AutoField(primary_key=True)
company = models.ForeignKey(Company)
weekday = models.IntegerField(choices=mytime.getweekdays())
fromHour = models.TimeField(choices=mytime.gettime12())
fromMinute = models.TimeField(choices=mytime.getminutes())
toHour = models.TimeField(choices=mytime.gettime12())
toMinute = models.TimeField(choices=mytime.getminutes())
'''
def __str__(self):
return "%s %s (%s - %s)" % (self.company, self.weekday, self.fromHour, self.toHour)
here is my views
#login_required
def addprofile(request):
current_user = request.user
#OpeningHoursFormSet = modelformset_factory(OpeningHours, form=OpeningHoursForm,extra=1)
if request.session['entry_count'] > 1:
messages.success( request, 'You can only create two business profiles now' )
return HttpResponseRedirect( reverse('home') )
else:
if request.method == 'POST':
form = OpeningHoursForm(request.POST)
if form.is_valid():
model_instance = form.save(commit=False)
model_instance.company ="thiscompany"
model_instance.weekday = request.POST.get('weekday')
model_instance.save()
else:
print("problems saving edited form")
return HttpResponseRedirect('/bizprofile/success')
else:
form = OpeningHoursForm()
context = {'form': form}
return render_to_response('bizprofile/addprofile.html', context, context_instance=RequestContext(request))
here is the form
{% extends "bizprofile/bizprofilebase.html" %}
{% block content %}
{% if form.subject.errors %}
<ol>
{% for error in form.subject.errors %}
<li><strong>{{ error|escape }}</strong></li>
{% endfor %}
</ol>
{% endif %}
{% if user.is_authenticated %}
<p>Welcome, {{ user.get_username }}. Thanks for logging in.</p>
<form method="post" action="">
{% csrf_token %}
<table>
{{form}}
</table>
<input type="submit" value="Submit Form"/>
</form>
{% else %}
<p>Welcome, new user. Please log in.</p>
{% endif %}
{% endblock %}
The problem lies in the fact that OP is using CharField for weekday data type, but the choices returned from a function are defined as integers. Since they are not compatible, the data could not be saved.