I have a simple project that has two different models. One that handles a report and one that stores the images for each report connected by a ForeignKey:
class Report(models.Model):
report_location = models.ForeignKey(Locations, on_delete=models.CASCADE)
timesheet = models.ImageField(upload_to='report_images', default='default.png')
text = models.CharField(max_length=999)
report_date = models.DateField(auto_now=True)
created_by = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return f"{self.report_location, self.report_date, self.created_by}"
class TimeSheetAndPics(models.Model):
report = models.ForeignKey(Report, on_delete=models.CASCADE)
report_images = models.ImageField(upload_to='report_images', default='default.png')
date = models.DateField(auto_now=True)
def __str__(self):
return f"{self.report} on {self.date}"
My Goal is to have a user fill out the report and then upload multiple pictures, however i cannot figure out how to handle multiple image uploads.
I have two forms for each model:
class ReportCreationForm(ModelForm):
class Meta:
model = Report
fields = [
'report_location',
'text',
]
class TimeSheetAndPicForm(ModelForm):
report_images = forms.FileField(widget=ClearableFileInput(attrs={'multiple': True}))
class Meta:
model = TimeSheetAndPics
fields = [
'report_images',
]
And this is how i try to handle my views:
class NewReport(LoginRequiredMixin, View):
def get(self, request):
context = {
'create_form': ReportCreationForm(),
'image_form': TimeSheetAndPicForm(),
}
return render(request, 'rakan/report_form.html', context)
def post(self, request):
post = request.POST
data = request.FILES or None
create_form = ReportCreationForm(post)
image_form = TimeSheetAndPicForm(post, data)
if create_form.is_valid() and image_form.is_valid():
clean_form = create_form.save(commit=False)
clean_form.created_by = request.user
clean_form.save()
clean_image_form = image_form.save(commit=False)
for images in clean_image_form:
clean_image_form.report = clean_form
clean_image_form.report = images
clean_image_form.save()
return redirect('rakan:rakan_index')
return redirect('rakan:new-report')
I have tried to solve this in different ways but i unfortunately hit a wall. I cant seem to find a solution that actually works. My best try i was able to save only 1 image in the models instead of the 3 test images.
I dont believe it makes a difference but here is also the HTML File that uses the forms:
<div class="content-section">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
<fieldset class="form-group">
<legend class="border-bottom mb-4">Create Report</legend>
{{ create_form }}
<br>
{{ image_form }}
</fieldset>
<div class="form-group">
<button class="btn btn-outline-info" type="submit">submit</button>
</div>
</form>
</div>
Anyone dealt with this problem would like to help me achieve a solution i would be very thankful. Thank you.
Related
im working on a sales team project where i'm trying to implement a function to upload their daily visit plan. While uploading i want to put a validation that, if visit for one customer is already uploaded then for same another wouldn't be uploaded and would raise a error like" Visit plan for this customer already exists on this date". I have read some where that unique together would help me to validate but im not sure how to use unique together in View. please find the below codes for your reference and help. Thanks in advance
Model:
class BeatPlan(models.Model):
beat_id = models.CharField(unique=True, max_length=15, null=True)
beat_dealer = models.ForeignKey(Dealer, null=True, on_delete=models.SET_NULL)
beat_user = models.ForeignKey('account.CustomUser', null=True, on_delete=models.SET_NULL)
beat_stake_holder = models.ForeignKey(StakeHolder, null=True, on_delete=models.SET_NULL)
beat_date = models.DateField(null=True)
create_date = models.DateField(null=True)
beat_status = models.CharField(choices=(('Not visited', 'Not visited'), ('Visited', 'Visited')),
default='Not visited', max_length=40, null=True)
beat_location = models.CharField(max_length=200, null=True)
beat_type = models.CharField(choices=(('Not planned', 'Not planned'), ('Planned', 'Planned')), max_length=50,
null=True)
beat_reason = models.CharField(max_length=200, null=True)
class Meta:
unique_together = ('beat_date', 'beat_dealer')
def __str__(self):
return str(self.beat_user) + str(self.beat_dealer) + str(self.beat_date)
View:
def upload_beat(request):
global u_beat_dealer, beat_user_id, beat_date
template = "upload_beat.html"
data = BeatPlan.objects.all()
l_beat = BeatPlan.objects.last()
l_beat_id = l_beat.id
print(l_beat_id)
current_month = datetime.datetime.now().strftime('%h')
# prompt is a context variable that can have different values depending on their context
prompt = {
'order': 'Order of the CSV should be name, email, address, phone, profile',
'profiles': data
}
# GET request returns the value of the data with the specified key.
if request.method == "GET":
return render(request, template, prompt)
csv_file = request.FILES['file']
# let's check if it is a csv file
if not csv_file.name.endswith('.csv'):
messages.error(request, 'THIS IS NOT A CSV FILE')
data_set = csv_file.read().decode('UTF-8')
# setup a stream which is when we loop through each line we are able to handle a data in a stream
io_string = io.StringIO(data_set)
next(io_string)
for column in csv.reader(io_string, delimiter=',', quotechar="|"):
u_beat_user_id = column[0]
u_beat_dealer = column[1]
beat_date = column[2]
print(u_beat_user_id)
if StakeHolder.objects.filter(unique_id=u_beat_user_id).exists():
user_id = StakeHolder.objects.get(unique_id=u_beat_user_id)
beat_user_id = user_id.id
if Dealer.objects.filter(dealer_code=u_beat_dealer).exists():
dealer_id = Dealer.objects.get(dealer_code=u_beat_dealer)
u_beat_dealer = dealer_id.id
l_beat_id += 1
u_beat_id = current_month + str(l_beat_id)
newBeatPlan = BeatPlan(beat_id=u_beat_id, beat_dealer_id=u_beat_dealer,
beat_stake_holder_id=beat_user_id,
beat_date=beat_date)
newBeatPlan.save()
messages.success(request, ' Beat added')
else:
messages.error(request, 'Dealer id does not exists')
else:
messages.error(request, "Unique ID doesn't exists")
context = {'beat_user': beat_user_id, 'dealer_code': u_beat_dealer, 'visit_date': beat_date}
return render(request, template, context)
Template:
{% extends 'base.html' %}
{% load static %}
{% block content %}
<h3 style="margin-left: 10px">Upload Beat</h3>
<div class="row" style="margin-left: 5px">
<div class="col-md-6">
<hr>
{{ order }}
<form action="" method="POST" enctype="multipart/form-data">
{% csrf_token %}
<label for="file1"> Upload a file</label>
<input type="file" id="file1" name="file" required><br><br><br><br><br>
<small>Only accepts CSV files</small>
<button class="btn btn-primary if-error-not-found" type="submit"
name="submit">Upload</button>
<a href="{% url 'beatplan' %}">
<div class="btn btn-primary back-button">Back</div>
</a>
</form>
<br>
<br>
</div>
unique_together validates the data that is saved on the database. However, you can still submit forms where those 2 fields are not unique and get an error on your backend. What you need to do is to handle that error manually. Django forms have a method add_error(field, error). However, as I see, you do not use them, so you can just add an error parameter while rendering your template. Manually check if the customer has an input for that date, and add that error without saving anything to your database. Must look like this.
context['error'] = false
if YourModel.objects.filter(fields1=field1, field2=field2).exists():
# your code that does not save anything
context['error'] = true
return render(request, template, context)
And handle the error on your template however you want.
unique_together will make sure that whatever you do, invalid data is not saved, so keep it.
I am trying to create a basic personality test in Django as a proof-of-concept at work. I'm new to Django (and python in general), coming at it from a C# .NET background.
I am trying to make a list of form objects (populated with information pulled from question objects stored in the database), then display them in the HTML.
This is only partly working; I can render the form attributes individually in a for loop (by calling, for example, question.pk) but nothing renders with the standard Django {{ form }} tag, and trying to submit the list of forms breaks the whole thing.
I'm pretty sure it's an issue with handling a bunch of form objects populated inside one larger html , but I'm not sure how to go about resolving it.
I've done some research into formsets, but I can't find any way to pre-populate the form items with information from the database.
Thanks in advance!
DISCQuestionForm in forms.py:
class DISCQuestionForm(forms.Form):
# create new form object from database question object
def __init__(
self,
pk,
disc_query,
dom_answer,
infl_answer,
stead_answer,
con_answer,
):
super().__init__()
self.pk = pk
self.disc_query = disc_query
self.dom_answer = dom_answer
self.infl_answer = infl_answer
self.stead_answer = stead_answer
self.con_answer = con_answer
self.disc_response = forms.DecimalField(
max_value=4,
widget=forms.NumberInput
)
disc_create method in views.py
# Create a new DISC assessment for current user
def disc_create(request, pk):
profile = User.objects.get(pk=pk)
user = int(profile.pk)
name = profile.name
rawquestionset = DISCQuestion.objects.all()
discformset = []
for item in rawquestionset:
question = DISCQuestionForm(
pk=item.pk,
disc_query=item.disc_query,
dom_answer=item.dom_answer,
infl_answer=item.infl_answer,
stead_answer=item.stead_answer,
con_answer=item.con_answer,
)
discformset.append(question)
if request.method == 'POST':
questionset = discformset[request.POST]
if questionset.is_valid():
dom = 0
infl = 0
stead = 0
con = 0
for discquestion in questionset:
if discquestion.disc_response == discquestion.dom_answer:
dom += 1
if discquestion.disc_response == discquestion.infl_answer:
infl += 1
if discquestion.disc_response == discquestion.stead_answer:
stead += 1
if discquestion.disc_response == discquestion.con_answer:
con += 1
disctest = DISCTest(
user=user,
name=name,
dom=dom,
infl=infl,
stead=stead,
con=con,
)
disctest.save()
else:
questionset = discformset
context = {
"pk": user,
"name": name,
"discquestionset": questionset
}
return render(request, "disc_create.html", context)
DISCTest and DISCQuestion models in models.py:
class DISCTest(models.Model):
user = models.ForeignKey('User', on_delete=models.CASCADE)
name = user.name
created_on = models.DateTimeField(auto_now_add=True)
dom = models.DecimalField(max_digits=3, decimal_places=0)
infl = models.DecimalField(max_digits=3, decimal_places=0)
stead = models.DecimalField(max_digits=3, decimal_places=0)
con = models.DecimalField(max_digits=3, decimal_places=0)
class DISCQuestion(models.Model):
disc_query = models.TextField()
disc_response = models.DecimalField(max_digits=1, decimal_places=0, null=True)
dom_answer = models.DecimalField(max_digits=1, decimal_places=0)
infl_answer = models.DecimalField(max_digits=1, decimal_places=0)
stead_answer = models.DecimalField(max_digits=1, decimal_places=0)
con_answer = models.DecimalField(max_digits=1, decimal_places=0)
and finally disc_create.html in templates:
{% extends "base.html" %}
{% block page_content %}
<div class="col-md-8 offset-md-2">
<h1>Take your DISC assessment</h1>
<hr>
<h3>Insert instructions here</h3>
<hr>
<form action="/assessment/create/{{pk}}/" method="post">
{% csrf_token %}
<div>
{% for question in discquestionset %}
<p>{{question.pk}}</p>
<p>{{ question.disc_query }}</p>
{{ form }}
{% endfor %}
</div>
<button type="submit">Submit</button>
</form>
</div>
{% endblock %}
Your DiscQuestionForm has no fields. disc_response is defined as an attribute of the form but for Django it isn't a field because it isn't added to self.fields. And form isn't defined in your template in your for loop, only question (which is the form) so {{ question }} would print the form if it had any fields.
But then the problem is that each of your question form fields would all have the same "name" attributes because they are not prefixed to make them unique.
You should read this document carefully to understand ModelForm and modelformset. Basically you need:
class DISCQuestionForm(forms.ModelForm):
class Meta:
model = DISCQuestion
def __init__(...):
...
Use the modelformset_factory to create a proper ModelFormSet that you can initialise with the request.POST when submitted.
DISCQuestionFormSet = modelformset_factory(DISCQuestionForm, form = DISCQuestionForm) # note DISCQuestionForm not needed if you don't customise anything in your form.
and in your view:
formset = DISCQuestFormSet(request.POST or None)
then in your template you can loop through the forms in the formset:
{% for form in formset %}{{ form }}{% endfor %}
I do really know that is a common topic but I've already checked every solution i could find and it did not work for me.
I would like to add user points using button click.
views.py
def add_points(request):
if request.GET.get('mybtn'):
profil = get_object_or_404(Profile, created_by=request.user)
profil.points += 10
profil.save(update_fields=["points"])
return render(request, 'users/profile.html')
models.py
class Profile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
image = models.ImageField(default='default.jpg', upload_to="profile_pics")
points = models.PositiveIntegerField(default=0)
people = models.PositiveIntegerField(default=0)
html file
<div>
<form method="POST">
{% csrf_token %}
<input type="submit" class="btn" value="+10 pkt" name="mybtn"/>
</form>
</div>
I have tried diffrent solutons but nothing really worked out for me.
Try F function.
from django.db.models import F
def add_points(request):
if request.GET.get('mybtn'):
profil = get_object_or_404(Profile, created_by=request.user)
profil.points = F('points') + 10
profil.save(update_fields=["points"])
return render(request, 'users/profile.html')
I have two UpdateViews, one works and the other doesn't... Please see Update IV
The working model is:
views.py
class JuryUpdate(UpdateView):
model = Jury
fields = [
'jury_name',
]
template_name_suffix = '_update_form'
def get_object(self, *args, **kwargs):
return get_object_or_404(Jury, jury_id=self.kwargs['jr'])
def form_valid(self, form):
form.instance.customer_id = self.kwargs['pk']
form.instance.court_year = self.kwargs['yr']
form.instance.jury_id = self.kwargs['jr']
return super(JuryUpdate, self).form_valid(form)
templates/jury_update_form.html (in relevant part)
<div class="container">
<h5>Update {{for.instance.jury_name}}</h5>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save" />
</form>
</div>
This setup will render an updateview with the object labels and existing field data from the object. This next setup doesn't work...
views.py
class CustomerUpdate(UpdateView):
model = Customer
fields = [
'customer',
]
template_name_suffix = '_update_form'
def get_object(self, *args, **kwargs):
return get_object_or_404(Customer, customer_id=self.kwargs['pk'])
def form_valid(self, form):
form.instance.customer_id = self.kwargs['pk']
return super(CustomerUpdate, self).form_valid(form)
templates/customer_update_form.html (in relevant part)
<div class="container">
<h5>Update {{form.instance.customer}}</h5>
<form method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Save" />
</form>
</div>
The second updateview does provide an update form but it doesn't populate with the calling object's data. It would seem that the object is there since the {{form.instance.customer}} contains the correct customer data for the call (no different than the JuryUpdate view).
I've tried to explicitly call get_initial (as described here) and print, but the result is {}. I've also tried variation of the form_valid call but as presented above, I believe I'm getting the correct object. There are several examples (such as here) that use get_initial to pre-populate with existing information - but that doesn't work in this instance and it isn't needed in my JuryUpdate view.
Any help is appreciated.
UPDATE I
models.py
class Customer(models.Model):
class Meta:
verbose_name = "Customer"
verbose_name_plural = "Customers"
customer_id = models.AutoField(
primary_key=True)
customer = models.CharField(
max_length=40)
# table fields
def get_absolute_url(self):
return reverse(
'customer-detail-view',
kwargs={'pk':self.pk})
def __str__(self):
return self.customer
class Jury(models.Model):
class Meta:
verbose_name = "Jury"
verbose_name_plural = "Juries"
customer = models.ForeignKey(
Customer,
on_delete=models.CASCADE)
court_year = models.ForeignKey(
CourtYear,
on_delete=models.CASCADE)
jury_id = models.AutoField(
primary_key=True)
jury_name = models.CharField(
max_length=20)
# other table fields
def get_absolute_url(self):
return reverse(
'jury-list-view',
kwargs={'pk':self.customer_id, 'yr':self.court_year_id})
def __str__(self):
return self.jury_name
urls.py
path('add_customer/', views.CustomerCreate.as_view(), name='customer-add'),
path('<int:pk>/', views.CustomerDetailView.as_view(), name='customer-detail-view'),
path('<int:pk>/delete/', views.CustomerDelete.as_view(), name='customer-delete'),
path('<int:pk>/update/', views.CustomerUpdate.as_view(), name='customer-update'),
path('<int:pk>/<int:yr>/', views.JuryListView.as_view(), name='jury-list-view'),
path('<int:pk>/<int:yr>/add_jury/', views.JuryCreate.as_view(), name='jury-add'),
path('<int:pk>/<int:yr>/<int:jr>/updatejury', views.JuryUpdate.as_view(), name='jury-update'),
path('<int:pk>/<int:yr>/<int:jr>/deletejury', views.JuryDelete.as_view(), name='jury-delete'),
UPDATE II
I've added a get_initial() method to my CustomerUpdate(UpdateView) as follows:
def get_initial(self):
initial = super(CustomerUpdate, self).get_initial()
print('initial data', initial)
customer_object = self.get_object()
initial['customer'] = customer_object.customer
# other fields omitted...
print('initial data updated', initial)
return initial
The initial data print returns {}. The initial data updated print returns {'customer': 'John Doe'} (plus the "other fields"). So it seems that the right information is getting pulled and delivered - It must be in the html?
Update III
I've taken the CustomerUpdate(UpdateView) down to the very basic class model:
class CustomerUpdate(UpdateView):
model = Customer
fields = [
'customer',
]
template_name_suffix = '_update_form'
The template is already the basic format (docs) - the rendered webpage still doesn't have object data for updating...
Update IV
I think I've figured out the problem - but don't know how to fix...
When I use the JuryUpdate call the console shows:
[02/Jun/2018 16:19:19] "GET /myapp/1/3/9/updatejury/?csrfmiddlewaretoken=1kHK4xgqdbBfXsv6mtz0WKgKpewFwLVtpUX5Z51qnLsGaMDVmpdVHKslXAXPhvY8 HTTP/1.1" 200 3687
When I use the CustomerUpdate call the console shows:
[02/Jun/2018 16:18:57] "POST /myapp/5/update/ HTTP/1.1" 200 3354
So my updateview on the Jury update is a GET call while my udpateview on Customer is aPOST call. In looking through the docs, I can see that the GET call with show the data while the POST call is (I think) assuming a black data set. I can't figure out why I'm getting a different result - where would this be set/changed?
After 3 days - I traced the issue - it had nothing to do with the view, model, url or the update template. The offending code was actually attached to the update button. Very specifically the page that had the button for "Update" used the following <form> code:
<form action="{% url 'customer-update' pk=customer.client_id %}" method="post" style="display: inline;">
{% csrf_token %}
<button type="submit" class="btn btn-outline-primary btn-custom-xs">U</button>
</form>
In the form call the method used was "POST" - and although I don't exactly understand the intracacies, the result is a blank UpdateView. The following code in the calling page fixed the problem.
<form action="{% url 'customer-update' pk=customer.client_id %}" style="display: inline;">
{% csrf_token %}
<button type="submit" class="btn btn-outline-primary btn-custom-xs">U</button>
</form>
I have a form that requires a URL as input. I would like to check if the URL begins with http:// or https://.
If it doesn't have one of these two 'stings' in the beginning, the form submission should give an error.
I don't have any clue on how to get started with this and could not find any info based on my limited knowledge of django and I have no clue what search terms to look up.
A basic hint would be a great help.
Thanks!
My current forms.py has a form based on a model:
class AddUrlForm(forms.ModelForm):
class Meta:
model = forwards
# fields = '__all__'
exclude = ["user", "counterA", "counterB", "shortcodeurl", "uniqueid"]
models.py:
class forwards(models.Model):
uniqueid = models.AutoField(primary_key=True)
user = models.CharField(max_length = 150)
urlA = models.CharField(verbose_name="URL Version A", max_length = 254)
counterA = models.DecimalField( max_digits=19, decimal_places=0,default=Decimal('0'))
urlB = models.CharField(verbose_name="URL Version B",max_length = 254)
counterB = models.DecimalField( max_digits=19, decimal_places=0,default=Decimal('0'))
timestamp = models.DateTimeField('date created', auto_now_add=True)
shortcodeurl = models.CharField(max_length = 254)
html segment where that shows how the form is integrated:
<form method="post">
{% csrf_token %}
{% for field in forwardform %}
<span>{{ field.label_tag }} </span>
<p style="color: black">{{ field }} </p>
{% for error in field.errors %}
<p style="color: red">{{ error }}</p>
{% endfor %}
{% endfor %}
<button class="btn btn-outline btn-xl type="submit">Generate URL</button>
</form>
views.py:
def forwardthis(request):
forwardform = AddUrlForm(request.POST or None)
if forwardform.is_valid():
forward = forwardform.save(commit=False)
forward.user = request.user.username
forward = forwardform.save()
uniqueid_local = forward.uniqueid
uniqueid_local_bytes = uniqueid_local.to_bytes((uniqueid_local.bit_length() + 7) // 8, byteorder='little')
shortcodeurl_local = urlsafe_base64_encode(uniqueid_local_bytes)
forward.shortcodeurl = shortcodeurl_local
forward.save()
return HttpResponseRedirect('/forwardthis')
query_results = forwards.objects.filter(user=request.user.username)
query_results_qty = query_results.count()
click_results = clickstats.objects.filter(user=request.user.username)
template = loader.get_template('forwardthis.html')
context = {
'forwardform': forwardform ,
'query_results':query_results,
'query_results_qty': query_results_qty
}
return HttpResponse(template.render(context,request))
You can create a validation method for each form field. def clean_FIELDNAME(). I supose the url field is shortcodeurl:
class AddUrlForm(forms.ModelForm):
def clean_shortcodeurl(self):
cleaned_data = self.clean()
url = cleaned_data.get('shortcodeurl')
if not is_valid_url(url): # You create this function
self.add_error('shortcodeurl', "The url is not valid")
return url
class Meta:
model = forwards
# fields = '__all__'
exclude = ["user", "counterA", "counterB", "shortcodeurl", "uniqueid"]
For anyone coming here in 2021.
Nowadays Django provides the tools to achieve this kind of validation.
Based on Django 3.2 documentation
from django import forms
from django.core.exceptions import ValidationError
class AddUrlForm(forms.ModelForm):
class Meta:
model = forwards
# fields = '__all__'
exclude = ["user", "counterA", "counterB", "shortcodeurl", "uniqueid"]
def clean_shortcodeurl(self):
data = self.cleaned_data['shortcodeurl']
if "my_custom_example_url" not in data:
raise ValidationError("my_custom_example_url has to be in the provided data.")
return data