I'm working on an app that will let users build their own databases, and one of the fields they can create is a photo field. I'm using a form model, and while it captures the rest of the data, it won't try to grab the files.
I have file uploads working elsewhere on the site, and if I upload the file manually in the admin panel it shows up just fine, but the form itself won't grab the file.
In my template, I have:
<form action="/orgs/{{org.id}}/db/{{db.unique_id}}/rows/add/" method="post" enctype="multipart/form-data">
{% csrf_token %}
...
{% elif column.field_type = 23 %}
{{form.photofield}}
{% endif %}
</form>
In my models:
from django.db import models
def get_upload_file_name(instance, filename):
return settings.UPLOAD_FILE_PATTERN % (str(time()).replace('.','_'), filename)
class DbField(models.Model):
...
photofield = models.FileField(upload_to=get_upload_file_name, null=True, blank=True)
in my forms:
from django import forms
from byodb.models import DbField
class DbFieldForm(forms.ModelForm):
class Meta:
model = DbField
fields = ('photofield',)
widgets = {'photofield': forms.FileInput()}
and in my views:
def org_database_add_row(request, organization_id=1, db_id=1):
if request.POST:
if request.POST.get(str(column.unique_id)):
if column.field_type == 23:
form = DbFieldForm(request.POST, request.FILES)
if form.is_valid():
f = form.save(commit=False)
...
f.save()
sorry for any confusion about column/row stuff, it's just data to place the field. If you need more detail then I can add more, but on the surface it looks like everything SHOULD work...
ALTERNATIVELY, I have been trying to get it to work another way, avoiding using the form altogether so that the file field will be named the same as my column.
In that instance, in my template it reads:
<input type="field" name="{{column.unique_id}}" id="photofield" />
and in my views it reads:
elif column.field_type == 23:
DbField.objects.create(row=row, column=column, unique_id=column.unique_id, creator=request.user, last_editor=request.user, field_type=column.field_type, photofield=request.FILES[str(column.unique_id)])
However it's the same issue, it will create the field just fine, but it won't try to grab the file. I'm not sure why it's only failing here, as this works everywhere else on the site.
My bad, I figured out the problem.
In my views, where I had:
if request.POST:
if request.POST.get(str(column.unique_id)):
if column.field_type == 23:
DbField.objects.create(row=row ... photofield=request.FILES[str(column.unique_id)])
It was failing because request.POST.get(str(column.unique_id)) was empty since it was FILES and not POST.
I rewrote the view to accomodate:
if request.POST.get(str(column.unique_id)):
...
else:
if request.FILES[str(column.unique_id)]:
if column.field_type == 23:
DbField.objects.create(row=row ... photofield=request.FILES[str(column.unique_id)])
else:
DbField.objects.create(row=row ... all other fields left blank)
This way if the request.POST for the field comes up empty, it checks to see if there's a file attached and the correct field type, if not then it just creates the empty field.
Related
I am trying to implement a tagging process for profiles so you can add your hobbies for example.
I have chosen django-taggit as it seemed quite simple and does what I need it to, plus don't really know how to do it myself from scratch.
I have managed to make it work to some extent but I am having issues with 3 things:
Not really sure what's the best way to control the form field for these tags as I generate the form automatically with widget adjustments in meta function of the form, but it might work fine after resolving the below two issues.
When there is no data for the field hobbies (tags) the field gets populated with a single tag of value "[]" as per below image.
When I add a tag of "music" and submit the form after I reload the page I get this "[]" as per image. I assumed this will be dealt with by the library, but I cannot see another similar scenario online.
When I try adding another tag of "games" and save and reload, the below happens. The initial value gets wrapped again.
My model is:
class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
hobbies = TaggableManager()
My form is:
class UserProfileForm(forms.ModelForm):
class Meta:
model = UserProfile
fields = ['hobbies',]
def __init__(self, *args, **kwargs):
super(UserProfileForm, self).__init__(*args,**kwargs)
self.fields['hobbies'].widget = forms.TextInput()
self.fields['hobbies'].widget.attrs['data-role'] = "tagsinput"
self.fields['hobbies'].widget.attrs['class'] = "form-control"
self.fields['hobbies'].required = False
My view function is:
if request.method == 'POST':
user_profile = UserProfile.objects.get(user=request.user)
form = UserProfileForm(request.POST, instance=user_profile)
print(form)
if form.is_valid():
obj = form.save(commit=False)
obj.user = request.user
obj.save()
print("Form valid")
form.save_m2m()
Using:
<script src="/static/js/tagsinput.js"></script>
<link rel="stylesheet" href="{% static 'css/tagsinput.css' %}" />
I had this exact same problem.
One solution is to apply the data-role="tagsinput" AFTER you turn a list of tags into a comma-separated string for the form.
Here is that solution:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
def __init__(self, **kwargs):
self.fields['tags'].widget.attrs['value'] = ", ".join(list(self.instance.tags.names()))
self.fields['tags'].widget.attrs['data-role'] = "tagsinput"
Output:
As you can see, there's a problem with quotes appearing around tags that are multi-word. It also causes new tags with quotes to be saved to the database.
If double-quotes didn't appear around multi-word phrases, this would be the most elegant solution. If someone solves this in the future, drop a note!
My template is this:
<div class="m-3 p-3 border">
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
{{ form|crispy }}
<button class="btn btn-primary" type="submit">Save Form</button>
</form>
</div>
I know I can use a template tag to strip the extra quotes from the tag field itself, but then I'd have to go through and create all the form fields manually just to set the tags template tag.
For the time being, my solution is to simply use Javascript and just modify the Meta widgets section of the form.
FINAL ANSWER (for now):
forms.py
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
widgets = {
'tags': forms.TextInput(attrs={
"data-role": "tagsinput",
})
}
custom.js - put this script on the page that loads the form.
document.addEventListener("DOMContentLoaded", function(event) {
let tags_input = document.querySelector('#id_tags');
let tags_input_value = tags_input.value;
let new_value = [...tags_input_value.matchAll(/<Tag:\s*([\w\s]+)>/g)].map(([, m]) => m).join(', ')
tags_input.setAttribute('value', new_value);
}
So all we're doing is modifying the front-end presentation, and leaving all the backend internal forms functionality untouched.
So after quite a few (hundreds) of tests, I finally narrowed down where the issue was and tried to go around it with successful result.
It seems the data got amended into tag objects through tagsinput library I was using. Only when the "data-role" was specified as "tagsinput" in the forms.py the data would already come to html side as those objects and be shown incorrectly. So instead I wanted to keep the data clean and only apply data-role='tagsinput' in the end for visual aspect, which I did using:
var hobbiesTags = document.getElementById("id_hobbies");
if(hobbiesTags){
var att = document.createAttribute("data-role");
att.value = "tagsinput";
hobbiesTags.setAttributeNode(att);
};
And that resulted in the below. Maybe there are better ways to do this, I'm not sure, but it's a pretty clean solution. Share your alternatives.
Background:
I'm building a personal dictionary web-application, and have a queryset of terms and definitions. In my web app, I have an edit page, where I want to show a ModelFormSet that allows the user to edit any/all entries, and delete them if needed - the form has a delete button for each row and a submit button to save changes to the form.
The current issue is that when I click on a set to edit, the formset shows up correctly, and when I change a term and hit "Submit" the form updates to show the change. However, when I go back to my "View Set" page, the term hasn't been updated, which I assume means the change didn't go through to the actual database - the form itself was simply submitted. This is what I would like to currently fix, and I also want to make it so that the delete button deletes the entry.
What I've Tried:
I've gone through every StackOverflow question I could find relating to the topic, and various solutions were: add an instance parameter when passing in "request.POST", re-initialize the formset after saving, change the "action" url in the HTML page, etc., but every solution I try either results in another error or doesn't change anything.
I also checked the documentation but the examples provided were for simpler examples, and I'm not sure where exactly my error is in this case.
Finally, I also used {{ form.errors }} to see if the forms were being validated, and this resulted in an empty bracket and no issues, so I think I know for sure that the form is being validated, and the POST request is working.
Code:
MODELS.PY
class Set(models.Model):
title = models.CharField(max_length = 64, null = False, blank = False)
description = models.CharField(max_length = 255, null = False, blank = True)
def __str__(self):
return self.title
class Entry(models.Model):
set = models.ForeignKey(Set, on_delete=models.CASCADE)
term = models.TextField()
definition = models.TextField()
def __str__(self):
return self.term
FORMS.PY
from django.forms import ModelForm
from django.forms.models import modelformset_factory
from .models import Set, Entry
class SetForm(ModelForm): # Form that maps to Set
class Meta:
model = Set
fields = ['title', 'description']
class EntryForm(ModelForm):
class Meta:
model = Entry
fields = ['set', 'term', 'definition']
VIEWS.PY
def editEntry(request, set_id):
EntryFormSet = modelformset_factory(Entry, EntryForm, extra=0)
set_obj=Set.objects.get(id=set_id)
entry_list = set_obj.entry_set.order_by("term")
entry_formset=EntryFormSet(queryset=entry_list)
if request.method == 'POST':
instances=entry_formset.save()
for instance in instances:
instance.save()
entry_formset = EntryFormSet(queryset=instances)
else:
entry_formset = EntryFormSet(queryset=entry_list)#formset_factory(entry_form)
return render (request, 'dictTemplates/editEntries.html', {'entry_formset': entry_formset})
EDIT ENTRIES.HTML
<h1 style="text-align:center"><strong></center>Edit Entries Page</strong></h1>
<form method="POST" action = "">
{% csrf_token %}
{{ entry_formset.management_form }}
<center><table id="entriesFormSet" class="table">
<input type ="submit" value ="Submit Form">
<tr>
<th><h3>Terms</h3></th>
<th><h3>Definitions</h3></th>
</tr>
<tbody>
{% for form in entry_formset %}
<tr>
<td>{{ form.term }}</td>
<td>{{ form.definition }}</td>
<td class="delete-entry-button"><input type = "submit" value = "Delete Term"></td>
</tr>
{% endfor %}
</tbody>
</table></center>
</form>
URLS.PY
urlpatterns = [
path('', views.home, name='Home'),
path('about/', views.about, name='About'),
path('sets/', views.sets, name='Sets'),
path('sets/create/', views.createSet, name='createSet'),
path('sets/edit/(?P<set_id>[\d]+)', views.editSet, name='editSet'),
path('sets/delete/(?P<set_id>[\d]+)', views.deleteSet, name='deleteSet'),
path('sets/view/(?P<set_id>[\d]+)', views.viewSet, name='viewSet'),
path('entrys/create/(?P<set_id>[\d]+)', views.createEntry, name='createEntry'),
path('entrys/edit/(?P<set_id>[\d]+)', views.editEntry, name='editEntry'),
path('entrys/delete/(?P<entry_id>[\d]+)', views.deleteEntry, name='deleteEntry'),
]
The desired result is that clicking "Submit" results in an updated form plus changes in the database, but right now, clicking "Submit" only results in an updated form at that moment - clicking to another page or any other action makes the form revert to the original state.
I think the problem is that my Submit button somehow isn't "mapping" to saving the form into the database, that there's a missing connection there somewhere, but I'm not sure how to find it.
Please let me know if I should format/post this question differently, as it's my first SO question. Thank you so much!! Any help would be very much appreciated!
Not sure if something else is also wrong, but your view doesn't instantiate your formset well. Try with the following code:
EntryFormSet = modelformset_factory(Entry, EntryForm, extra=0)
if request.method == 'POST':
entry_formset=EntryFormSet(data=request.POST)
if entry_formset.is_valid():
entry_formset.save() # this will save all the instances
else:
set_obj=Set.objects.get(id=set_id)
entry_list = set_obj.entry_set.order_by("term")
entry_formset=EntryFormSet(queryset=entry_list)
return render(request, 'dictTemplates/editEntries.html', {'entry_formset': entry_formset})
If you want you may assign return value to formset save like instances = entry_formset.save() and debug which instances are saved (that returned value will be a list of those that are saved to database) by passing it to template together with formset in context and show the value in template.
Edit: Mostly got this working with a clean update
I added docfile2 and docfile3 beyond the example I used to code this. I also changed the view to handle them:
#login_required(login_url='/ngasite/login/')
def list(request):
# Handle file upload
if request.method == 'POST':
form = DocumentForm(request.POST, request.FILES)
if form.is_valid():
for fieldname in ('docfile', 'docfile2', 'docfile3'):
file = form.cleaned_data[fieldname]
if file:
Document(docfile=file).save()
# Redirect to the document list after POST
return HttpResponseRedirect(reverse('ngasite:list', args=""))
else:
form = DocumentForm() # A empty, unbound form
# Load documents for the list page
documents = Document.objects.all()
paginator = Paginator(documents, 25)
page = request.GET.get('page')
try:
docs = paginator.page(page)
except PageNotAnInteger:
docs = paginator.page(1)
except EmptyPage:
docs = paginator.page(paginator.num_pages)
# Render list page with the documents and the form
return render_to_response(
'ngasite/list.html',
{'documents': documents, 'form': form, 'docs':docs},
context_instance=RequestContext(request)
)
This worked pretty well, I can
handle three files on my form now instead of one. But relisting after a post gives me an attribute has no file associated with it error. I am guessing because my checks for saving in the view is not really working correctly and saving regardless if there was a file or not. Maybe my python syntax is bad?
no errors int he console though.
This is what is failing in my list.html template:
{% if docs %}
<ul>
{% for doc in docs %}
<li>{{ doc.docfile.name }} </li>
{% endfor %}
</ul>
{% else %}
<p>No documents.</p>
{% endif %}
So my real forms.py with the overriding of the is_valid
from django import forms
class DocumentForm(forms.Form):
docfile = forms.FileField(
label='Select a file',
help_text='max. 7 Gigabytes',
required=False
)
docfile2 = forms.FileField(
label='Select a file',
help_text='max. 7 Gigabytes',
required=False
)
docfile3 = forms.FileField(
label='Select a file',
help_text='max. 7 Gigabytes',
required=False
)
def clean(self):
data = super(DocumentForm, self).clean()
if not (data['docfile'] or data['docfile2'] or data['docfile3']):
return ValidationError('You must upload at least one file.')
return data
What am i missing? my view fails on lines like
docfile=request.FILES['docfile'] cause it knows its missing, I thought my if newdoc2: etc would handle that but it doesnt work like I think it does/should
Ultimately as I learn more python and Django i want to turn this into a nice file uploader with responses to the browser to show the upload progress etc.
You should set your fields to required=False, which will avoid a validation error if any field is empty:
docfile3 = forms.FileField(
label='Select a file',
help_text='max. 7 Gigabytes',
required=False
)
Then in your form clean method check to see if at least one file is there:
from django.core.exceptions import ValidationError
def clean(self):
data = super(DocumentForm, self).clean()
# if there are already form errors, don't proceed with additional
# validation because it may depend on fields which didn't validate.
if self.errors:
return data
if not (data['docfile1'] or data['docfile2'] or data['docfile3']):
return ValidationError('You must upload at least one file.')
return data
I have not actually run this code so there may be some little issue but you get the idea...
In your view form_valid() method, there are problems with your saving code. Do something more like this:
for fieldname in ('docfile', 'docfile2', 'docfile3'):
file = form.cleaned_data.get(fieldname, None)
if file:
Document(docfile = file).save()
this is probably a question for absolute beginners since i'm fairly new to progrmaming. I've searched for couple of hours for an adequate solution, i don't know what else to do.
Following problem. I want to have a view that displays. e.g. the 5 latest entries & 5 newest to my database (just an example)
#views.py
import core.models as coremodels
class LandingView(TemplateView):
template_name = "base/index.html"
def index_filtered(request):
last_ones = coremodels.Startup.objects.all().order_by('-id')[:5]
first_ones = coremodels.Startup.objects.all().order_by('id')[:5]
return render_to_response("base/index.html",
{'last_ones': last_ones, 'first_ones' : first_ones})
Index.html shows the HTML content but not the content of the loop
#index.html
<div class="col-md-6">
<p> Chosen Items negative:</p>
{% for startup in last_ones %}
<li><p>{{ startup.title }}</p></li>
{% endfor %}
</div>
<div class="col-md-6">
<p> Chosen Items positive:</p>
{% for startup in first_ones %}
<li><p>{{ startup.title }}</p></li>
{% endfor %}
Here my problem:
How can I get the for loop to render the specific content?
I think Django show render_to_response in template comes very close to my problem, but i don't see a valid solution there.
Thank you for your help.
Chris
--
I edited my code and problem description based on the solutions provided in this thread
the call render_to_response("base/showlatest.html"... renders base/showlatest.html, not index.html.
The view responsible for rendering index.html should pass all data (last_ones and first_ones) to it.
Once you have included the template into index.html
{% include /base/showlatest.html %}
Change the view above (or create a new one or modify the existing, changing urls.py accordingly) to pass the data to it
return render_to_response("index.html",
{'last_ones': last_ones, 'first_ones' : first_ones})
The concept is that the view renders a certain template (index.html), which becomes the html page returned to the client browser.
That one is the template that should receive a certain context (data), so that it can include other reusable pieces (e.g. showlatest.html) and render them correctly.
The include command just copies the content of the specified template (showlatest.html) within the present one (index.html), as if it were typed in and part of it.
So you need to call render_to_response and pass it your data (last_ones and first_ones) in every view that is responsible for rendering a template that includes showlatest.html
Sorry for the twisted wording, some things are easier done than explained.
:)
UPDATE
Your last edit clarified you are using CBV's (Class Based Views).
Then your view should be something along the line:
class LandingView(TemplateView):
template_name = "base/index.html"
def get_context_data(self, **kwargs):
context = super(LandingView, self).get_context_data(**kwargs)
context['last_ones'] = coremodels.Startup.objects.all().order_by('-id')[:5]
context['first_ones'] = coremodels.Startup.objects.all().order_by('id')[:5]
return context
Note: personally I would avoid relying on the id set by the DB to order the records.
Instead, if you can alter the model, add a field to mark when it was created. For example
class Startup(models.Model):
...
created_on = models.DateTimeField(auto_now_add=True, editable=False)
then in your view the query can become
def get_context_data(self, **kwargs):
context = super(LandingView, self).get_context_data(**kwargs)
qs = coremodels.Startup.objects.all().order_by('created_on')
context['first_ones'] = qs[:5]
context['last_ones'] = qs[-5:]
return context
I have been looking at form validation in python using Django as this seems to be the default way to do it, other than checking each field, performing some validation and kicking out a specific message for each field that is badly formed. Ideally I want the benefits of form validation, but I do not know how to couple this Django way with the .css way I am displaying by form.
My form is templated HTML with a css behind to handle the display. The code uses data to send back the form if there is an issue and displays the form which was created previously. So in a nutshell, how do we couple validation to a pre formatted HTML form with css without individually validating.
Here is the code I am using:
Looking at those references etc http://www.djangobook.com/en/2.0/chapter07/, I have been unable to come up with a good way to couple everything together. The problem relates to send back the form and contents if the form is not valid. So what I am doing is pulling out each generated form item by item and displaying in the .html file.
So my question is. How do I get this working. Now I can display the form with css style sheet, but I cannot seem to get validation working on the field and I'm always generating an error.
class Quote(db.Model):
email = db.StringProperty(required=True)
class QuoteForm(djangoforms.ModelForm):
class Meta:
model = Quote
exclude = ['entry_time']
class MainPage(webapp.RequestHandler):
def get(self):
form = QuoteForm();
template_values = {}
path = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(path, {'form': form}))
def post(self):
data = QuoteForm(data=self.request.POST)
if data.is_valid():
# save here
self.redirect('/Confirm.html')
else:
template_values = {}
path = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(path, {'form': data}))
and the part of the .html file is here
<div>
{{ form.email.errors }}
<label for="id_email">Your e-mail address:</label>
{{ form.email }}
</div>
It would nothing that I put into the email field validates correctly. I'm not sure why!? I'm losing the information I have already put into the form. How do I retain this information and actually do proper validation. The model suggests that only a non blank string is required, but nothing ever satisfies the validation.
Thanks
"Customizing Form Design" section of http://www.djangobook.com/en/2.0/chapter07/ explains how to style forms to go with your HTML and CSS.
Looking at those references etc http://www.djangobook.com/en/2.0/chapter07/, I have been unable to come up with a good way to couple everything together. The problem relates to send back the form and contents if the form is not valid. So what I am doing is pulling out each generated form item by item and displaying in the .html file.
So my question is. How do I get this working. Now I can display the form with css style sheet, but I cannot seem to get validation working on the field and I'm always generating an error.
class Quote(db.Model):
email = db.StringProperty(required=True)
class QuoteForm(djangoforms.ModelForm):
class Meta:
model = Quote
exclude = ['entry_time']
class MainPage(webapp.RequestHandler):
def get(self):
form = QuoteForm();
template_values = {}
path = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(path, {'form': form}))
def post(self):
data = QuoteForm(data=self.request.POST)
if data.is_valid():
# save here
self.redirect('/Confirm.html')
else:
template_values = {}
path = os.path.join(os.path.dirname(__file__), 'index.html')
self.response.out.write(template.render(path, {'form': data}))
and the part of the .html file is here
<div>
{{ form.email.errors }}
<label for="id_email">Your e-mail address:</label>
{{ form.email }}
</div>
It would nothing that I put into the email field validates correctly. I'm not sure why!? I'm losing the information I have already put into the form. How do I retain this information and actually do proper validation. The model suggests that only a non blank string is required, but nothing ever satisfies the validation.