Please help me. I am new to Django, cannot undertsand the following thing - I have subclass of CreateView for creating a comment. I want to create a project where people can leave their comments and attach files (images) to this comment. One should have possibility to attach as many images as he wants to ONE form with text comment. I have found in Internet a decision that I need to use 2 models - 1 model for text comment + 1 separate model for images. Is it so?
Comment (text) form is created and handled in my views.py by sublass of CreateView. How to connect new separate model for images with my CreateView ?
models.py
class Descriptions(models.Model):
…
city = models.ForeignKey(Cities, on_delete=models.CASCADE)
description = models.TextField()
date_added = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, on_delete=models.DO_NOTHING)
…
class Description_Photos(models.Model):
image = models.ImageField(upload_to='images/', blank=True)
description = models.ForeignKey(Descriptions, on_delete=models.CASCADE, related_name='photos')
forms.py
class DescriptionsForm(forms.ModelForm):
class Meta:
model = Descriptions
exclude = []
widgets = {'description': forms.Textarea(attrs={'cols':90})}
class Photos_form(forms.Form):
photos = forms.FileField(widget=forms.FileInput(attrs={'multiple': True}))
views.py
class DescriptionCreate(CreateView):
model = Descriptions
form_class = DescriptionsForm
template_name = 'countries/new_description.html'
def get_success_url(self):
return reverse('countries:descr', args=[self.kwargs['country_id'], self.kwargs['city_id']])
def get_context_data(self, **kwargs):
self.city = get_object_or_404(Cities, id=self.kwargs['city_id'])
kwargs['city'] = self.city
return super().get_context_data(**kwargs)
def form_valid(self, form):
form.instance.city = get_object_or_404(Cities, id=self.kwargs['city_id'])
form.instance.owner = self.request.user
messages.success(self.request, 'Your post has been added, thank you')
return super().form_valid(form)
So my question is what should I write in views.py for class Photos_form(forms.Form): ? How to connect this class and my class DescriptionCreate(CreateView) ?
I use for same situations FormSets https://docs.djangoproject.com/en/2.0/topics/forms/formsets/
Declare FormSet for images models
…
# forms.py
class DescriptionsForm(forms.ModelForm):
class Meta:
model = Descriptions
exclude = []
widgets = {'description': forms.Textarea(attrs={'cols':90})}
class Photos_form(forms.Form):
photos = forms.FileField(widget=forms.FileInput(attrs={'multiple': True}))
##### Declare FORMSET !!! ###
class BasePhotosFormSet(BaseModelFormSet):
"""By default, when you create a formset from a model, the formset
will use a queryset that includes all objects in the model"""
def __init__(self, *args, **kwargs):
if 'city' in kwargs.keys():
city = kwargs.pop('city')
else:
city = None
super().__init__(*args, **kwargs)
if city and isinstance(instance, Cities):
self.queryset = Description_Photos.objects.filter(city=city)
else:
self.queryset = Description_Photos.objects.none()
# I usually declare formset for create operations and formset for update operations separately
PhotosCreateFormSet = forms.modelformset_factory(Description_Photos, Photos_form,
fields=Photos_form.Meta.fields, extra=0,
formset=BasePhotosFormSet)
PhotosUpdateFormSet = forms.modelformset_factory(Description_Photos, Photos_form, can_delete=True,
fields=PropertyImageForm.Meta.fields, extra=0,
formset=BasePhotosFormSet)
#############
# views.py
class DescriptionCreate(CreateView):
def __init__(self, **kwargs):
self.object = None
super().__init__(**kwargs)
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
if self.request.POST:
images_formset = PhotosCreateFormSet(self.request.POST, self.request.FILES, city=self.object)
else:
images_formset = PhotosCreateFormSet(instance=self.object)
context['formset'] = images_formset
context['city'] = self.object
return context
Templates
<div id="img-form-template" style="display: none">
<!- Declare EMPTY FORM for dynamically rebuild user interface by jQuery, for example->
{{ formset.empty_form }}
</div>
...
<div id="my-images">
...
{{ formset.management_form }}
{% for image_form in formset %}
{{ image_form }}
{% endfor %}
</div>
...
<script>
...
<!- Any javascript code to dynamically create empty form based on template "#img-form-template" ->
...
</script>
I've tried to rewrite my custom code to your variant.
I suppose it is a bad idea to declare self.city in your example as creation model instance: Django automatically create self.object
I hope you have found your answer, I have done something, but may not be the cleaned or recommended one;
I have a page for multiple images upload and another page to view the images, add additional information and complete the process. Between the pages, I have used session to pass a parent table ID through which I group multiple images being uploaded.
Below is my class for image upload:
class UploadImages(LoginRequiredMixin,CreateView):
In get_context_data, I do the below (summarized):
if "album_id" not in self.request.session:
print("Creating a temporary album!")
album = Album(
owner = self.request.user,
category_id = 6,#others
privacy_id=3,#restricted
status_id=8,#temporary file
title="Temporary Album for " + self.request.user.get_full_name(),
is_temp=True
)
album.save()
self.request.session['album_id'] = album.id
context.update({
'taid':album_id,
})
return context
When I re-direct to this view from the menu, all session IDs with album_id are clearer, this way it will get called for the first time. Each time a single file uploads, this method will be called inside form_valid and hence, it is good to validate and then create for the firs time. In the HTML template, I have this javascript variable to hold the redirect url after successful file upload, in ajax call:
<script>
var redirect_url = "{% url 'images:complete-upload' %}"
</script>
Then in form_valid:
if self.request.is_ajax():
album_id = self.request.session['album_id']
Now I have access to the album_id and I can use this to save multiple images, grouped into one album:
lnk_album = AlbumPhotos(
album_id = album_id,
photo = self.object
)
lnk_album.save()
Once this is done for each single message, my ajax call returns success, in the javascript file, I do this:
$("#fileupload").fileupload({
dataType: 'json',
sequentialUploads: true, /* 1. SEND THE FILES ONE BY ONE */
start: function (e) { /* 2. WHEN THE UPLOADING PROCESS STARTS, SHOW THE MODAL */
$("#modal-progress").modal("show");
},
stop: function (e) { /* 3. WHEN THE UPLOADING PROCESS FINALIZE, HIDE THE MODAL */
$("#modal-progress").modal("hide");
//write code to re-direct
window.location.replace(redirect_url);
},
progressall: function (e, data) { /* 4. UPDATE THE PROGRESS BAR */
var progress = parseInt(data.loaded / data.total * 100, 10);
var strProgress = progress + "%";
$(".progress-bar").css({"width": strProgress});
$(".progress-bar").text(strProgress);
},
done: function (e, data) {
console.log("completed file upload!")
if (data.result.is_valid) {
console.log("success!!")
}
else{
console.log(e)
}
}
});
This gets job done for me so far. I haven't figured out whether this is a best practice. Once I do a penetration testing, maybe in a month or so, after completing this app, I will know (or someone else points out the vulnerabilities in this code).
On the receiving page, I pick up the album_id from the session (get_context_data) and then delete it to ensure the session is clean.
I hope this helps.
Related
I'm looking for some advice where to go from here. I've been working on making a Form, which dynamically generates its fields.
The form is working and generating everything correctly. However, I am having issues with how to save the actual form data. I'm looking for each field to save as a new item in a model.
The View Class from view.py
class MaintenanceCheckListForm(LoginRequiredMixin, FormView):
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
form_class = MaintenanceCheckListForm
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
form.cleaned_data
for key, values in form:
MaintenanceCheckList.objects.create(
item = key,
is_compliant = values
)
return super().form_valid(form)
The Form from forms.py
class MaintenanceCheckListForm(forms.Form):
def __init__(self, *args, **kwargs):
super(MaintenanceCheckListForm, self).__init__(*args, **kwargs)
items = Maintenance_Item.objects.all()
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
for item in items:
self.fields[str(item.name)] = forms.ChoiceField(
label=item.name,
choices=CHOICES,
widget=forms.RadioSelect,
initial='F',
)
The Model, from models.py
class MaintenanceCheckList(CommonInfo):
CHOICES = (
('P','Compliant'),
('F','Non-Compliant'),
)
id = models.AutoField(primary_key=True)
item = models.CharField(max_length=100)
is_compliant = models.CharField(max_length=20, choices= CHOICES)
I am having trouble accessing the data from the Form when it POST's. I've done some troubleshooting where I have set the values statically in the '''form_valid''' and it appears to generate the correct amounts of entires in the model. However the trouble begins when I attempt to insert the values from the POST.
I receieve the below error, which I believe it is trying to dump all the keys and values into a single item instead of looping over each key, value and creating the item.
DataError at /maintenance/checklist
value too long for type character varying(100)
Request Method: POST
Request URL: http://t1.localhost:8000/maintenance/checklist
Django Version: 3.1.6
Exception Type: DataError
Exception Value:
value too long for type character varying(100)
I'm fairly new to the world of Django (4 weeks and counting so far, and maybe 12 weeks into python). So any assistance would be amazing!
I believe you have somewhat gone on a tangent. There's a simpler solution of using Model formsets for what you want.
First if you want a custom form make that:
from django import forms
class MaintenanceCheckListComplianceForm(forms.ModelForm):
item = forms.CharField(widget = forms.HiddenInput())
is_compliant = forms.ChoiceField(
choices=MaintenanceCheckList.CHOICES,
widget=forms.RadioSelect,
initial='F',
)
class Meta:
model = MaintenanceCheckList
fields = ('item', 'is_compliant')
Next use it along with modelformset_factory in your views:
from django.forms import modelformset_factory
class MaintenanceCheckListFormView(LoginRequiredMixin, FormView): # Changed view name was a bit misleading
login_url = '/accounts/login'
template_name = 'maintenance/checklist.html'
success_url = reverse_lazy('m-checklist')
def form_valid(self, form):
instances = form.save()
return super().form_valid(form)
def get_form_kwargs(self):
kwargs = super().get_form_kwargs()
kwargs['queryset'] = MaintenanceCheckList.objects.none()
kwargs['initial'] = [{'item': obj['name'], 'is_compliant': 'F'} for obj in Maintenance_Item.objects.all().values('name')]
return kwargs
def get_form(self, form_class=None):
kwargs = self.get_form_kwargs()
extra = len(kwargs['initial'])
form_class = modelformset_factory(MaintenanceCheckList, form=MaintenanceCheckListComplianceForm, extra=extra)
return form_class(**kwargs)
Now in your template:
<form method="post">
{{ form }}
</form>
Or manually render it:
<form method="post">
{{ form.management_form }}
{% for sub_form in form %}
Item: {{ sub_form.item.value }}
{{ sub_form }}
{% endfor %}
</form>
Note: The above usage is a bit weird due to the naming of the formset variable as form by the FormView you should look into improving that a bit.
Note: Looking at the implementation it feels a bit weird to do this. I would advice you to redesign your models a bit. Perhaps a foreign key between your models? It basically feels like you have duplicate data with this implementation.
I am trying to print the content fields from my database,
Here's my models.py file:
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
read_time = models.TimeField(null=True, blank=True)
view_count = models.IntegerField(default=0)
Here's my views.py file:-
class PostDetailView(DetailView):
model = Post
def get_object(self):
obj = super().get_object()
obj.view_count += 1
obj.save()
return obj
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
all_texts = {
'texts': context.content
}
print(all_texts[texts])
return context
I am trying to access all the data's from the content field from my database,
But the above way is not working, Is there any way I can access all the data's from the content field, because I have to perform some action on these fields, like calculate the read_time of any content, based on the length of it.
You do not have to override the .get_queryset(…) method [Django-doc] for that, since the object is already passed to the context. You can simply render it in the template with:
{{ object.content }}
In case you really need this in the context, you can implement this as:
class PostDetailView(DetailView):
model = Post
# …
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(
texts=self.object.content
)
return context
In case you need all post objects, you can add these to the context:
class PostDetailView(DetailView):
model = Post
# …
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context.update(
texts=self.object.content,
posts=Post.objects.all()
)
return context
and render these as:
{% for post in posts %}
{{ post.content }}
{% endfor %}
It might be better to work with an F expression [Django-doc] when incrementing the view counter to avoid race conditions:
class PostDetailView(DetailView):
model = Post
def get_object(self):
obj = super().get_object()
views = obj.view_count
obj.view_count = F('view_count') + 1
obj.save()
obj.view_count = views+1
return obj
Just query all objects and loop the queryset to manipulate them according to your needs like so:
def your_view(self, **kwargs):
# Get all table entries of Model Post
queryset = Post.objects.all()
# Loop each object in the queryset
for object in queryset:
# Do some logic
print(object.content)
[...]
return (..., ...)
first import models
from . models import Post
then in your function
data=Post.objects.values('content').all()
Now data have all the values in content field
data=[{'content':first_value},{'content':second_value},..like this...]
I'm trying to build forms linked to a PostgreSQL database using Django ModelForms. The template is rendering two of the fields(the ones with ManyToMany relationships), but it only gives me an empty box for "title".
This is my forms.py:
Forms.py:
class ProgramForm(forms.ModelForm):
class Meta:
model = Program
fields = ['function','task', 'title']
widgets = {
'function' : forms.Select,
'task' : forms.Select,
'title' : forms.Select,
}
This is my Models.py:
class Program(models.Model):
title = models.CharField(max_length=255)
function = models.ManyToManyField(function, related_name='programs')
task = models.ManyToManyField(Task, related_name='programs')
def __unicode__(self):
return self.title
class Task(models.Model):
tasknum = models.CharField(max_length=20)
taskname = models.CharField(max_length=100)
task_num_name = models.CharField(max_length=100)
function = models.ForeignKey(Function, related_name="tasks")
def __unicode__(self):
return self.task_num_name
class Function(models.Model):
function = models.CharField(max_length=50)
function_abrev = models.CharField(max_length = 25)
def __unicode__(self):
return self.function
Views.py:
def main(request):
return render (request, 'assignments/main.html')
def add_program(request):
form = ProgramForm()
return render (request, 'assignments/ad_form.html', {"form":form})
def link(request):
if request.method == 'POST':
form = ProgramForm(request.POST)
if form.is_valid():
return HttpResponse("we maybe getting somewhere")
else:
return HttpResponse("keep working")
I need a couple of things to happen:
I need for the "title" to render in the html page as a scroll down(the same way "function" and "task" appear.
I need to be able to save the relationships. The models are populated with all the information required with the exception of the relationships. The objective is for a staff member to be able to chose a "function", for that choice to act as a filter for the "task" scroll down(function and task have a OneToMany), and then allow them to choose any programs they want to add to their portfolio.
Any help will be much appreciated.
1. Title field in form
For this, I don't quite understand how the title field could be a scroll down the same way function and task are. Function and task are drop downs because they are manytomany fields linked to other models, meaning that the user has to pick which other objects in the Functions model and the Tasks model are to be linked. The title field, on the other hand, is just a CharField and so there is no defined set of things for the user to pick from. To allow the user to enter in the title for the Program, you should change the widget for title to Textarea() as such:
forms.py
from django.forms import ModelForm, Textarea
class ProgramForm(forms.ModelForm):
class Meta:
model = Program
fields = ['function','task', 'title']
widgets = {
'function' : forms.Select,
'task' : forms.Select,
'title' : Textarea(),
}
2. Save the Program from the form
To save the Program created by the user on staff member, simply add form.save() to your link(request) function:
views.py
def link(request):
if request.method == 'POST':
form = ProgramForm(request.POST)
if form.is_valid():
form.save()
return HttpResponse("we maybe getting somewhere")
else:
return HttpResponse("keep working")
Hope this helps!
I was able to do a query from views.py and pass if to the template.
Views.py
def function_page(request, Function_id):
assignments = Function.objects.get(id=Function_id)
programs = assignments.programs.all()
context = {
'assignments': assignments,
'programs' : programs
}
return render (request, 'function.html', context)
HTML
{% for program in programs %}
<option value="{{program.title}}">{{program.title}}</option>
{% endfor %}
I'm creating a project with django and am having trouble with some of the basics, my aim is to have a homepage with a search box. When a search term is entered, the server gathers data from various api's, saves the data to the database then directs the user to a results page which displays this data.
So far I have managed to create a page that takes the search term and saves the data to the database. However if I try to then redirect to a new page it breaks, specifically the data doesn't save to the database. I'm having real trouble finding information on what to do next.
I have a model:
class film(models.Model):
filmName = models.CharField(primary_key=True, max_length=120)
quotient = models.FloatField()
rating = models.FloatField()
gross = models.IntegerField()
star = models.CharField(max_length=120)
releaseDate = models.DateField()
def __unicode__(self):
return self.filmName
With a form:
class searchFilm(forms.ModelForm):
class Meta:
model = film
fields = ['filmName']
A two view methods:
def home(request):
title = "Culturnomicon"
form = searchFilm(request.POST or None)
if form.is_valid():
searchedFilm = form.save(commit = False)
fixedName = searchedFilm.filmName.replace(' ', '+')
url = 'http://api.themoviedb.org/3/search/movie?api_key=APIKEY&query='+fixedName
urlRequest = Request(url)
try:
reponse = urlopen(urlRequest)
filmData = reponse.read()
parsed_json = json.loads(filmData)
except URLError, e:
print "didnt work"
firstResult = parsed_json['results'][0]
filmId = str(firstResult['id'])
filmData = getFilmData(filmId)
if parsed_json['total_results'] != 0:
searchedFilm = saveFilm(searchedFilm, filmData)
searchedFilm.save()
context = {
"template_title": title,
"form": form,
}
return render(request, "home.html", context)
def results(request):
return render(request, "results.html", {})
and a template:
<form method="POST" action="{% url 'results' %}">
{{form}}
{% csrf_token %}
<input type="submit" value ="Search" class = 'btn'/>
</form>
Any advice on what to do next would be greatly appreciated.
I also have the feeling that having methods to gather the API information may be a bad idea, is it bad to do this and if so how can I fix it?
If all you really want to do is load a different template then you don't need to have a separate view, you just move the return render(..."results.html" inside of the if form.is_valid() statement after you've done all you need with the database.
You may need to extend the render call to include some context data about what results that template should actually display however.
If you do want a separate view, then you need to move the handling of the form to the other view.
def home(request):
title = "Culturnomicon"
form = searchFilm(request.POST or None)
if form.is_valid():
# Your logic
return render(request, "results.html", {})
context = {
"template_title": title,
"form": form,
}
return render(request, "home.html", context)
I have a model Attribute and Product, declared like this:
class Attribute(models.Model):
value = models.TextField()
owner = models.ForeignKey(User)
type = models.ForeignKey(AttributeType)
image = ImageField(upload_to='attributes', null=True, blank=True)
related_attribute = models.ManyToManyField('self', blank = True, null = True)
class BaseWorkspace(models.Model):
name = models.CharField(max_length=255)
owner = models.ForeignKey(User)
attributes = models.ManyToManyField('Attribute', blank = True, null = True)
created = CreationDateTimeField()
modified = ModificationDateTimeField()
comments = models.ManyToManyField('Comment', blank = True, null = True )
sort_order = models.IntegerField(blank = True)
class Product(BaseWorkspace):
project = models.ForeignKey('Project', related_name='products')
how can I establish m-m relationship using formsets? I have tried model formset factories like this:
AttributeFormset = modelformset_factory(Attribute, form=AttributeForm)
with this function in the generic view:
def form_valid(self, form):
f = form.instance
f.sort_order = Product.default_sort_order()
f.owner = self.request.user
f.project = get_object_or_404(Project, pk=self.kwargs['pk'])
context = self.get_context_data()
attribute_form = context['attribute_form']
if attribute_form.is_valid():
self.object = form.save()
attribute_form.instance = self.object
attribute_form.save()
return HttpResponseRedirect(reverse(self.get_success_url()))
else:
return self.render_to_response(self.get_context_data(form=form))
but I cannot get it to work. any ideas?
Try something like this:
from django.forms.models import modelformset_factory
def my_view_function(request) :
# not sure where the product whose formset we are working on comes from
product = <whatever>
AttributeFormSet = modelformset_factory(Attribute)
if request.method == "POST" :
# POST bound formset
formset = AttributeFormSet(request.POST, queryset=Attribute.objects.filter(product=product))
# If the entire formset is valid
if formset.is_valid() :
for form in formset:
# Save each form in the set
b = form.save()
else :
#There was an error (add a message using the messages framework?)
pass
else :
# initial formset w/o post
formset = AttributeFormSet(queryset=Attribute.objects.filter(product=product))
...
Its kind of hard to give you more specific answer, I think we would need the entire view function or view class if you are using class based views.
In your template, something as simple as this (from the docs) should do it.
<form method="post" action="">
{{ formset.management_form }}
<table>
{% for form in formset %}
{{ form }}
{% endfor %}
</table>
</form>
If you need the ability to add forms to the formset at runtime w/ javascript look at this: http://code.google.com/p/django-dynamic-formset/. Ive never used it, but at the very least it looks like a step in the correct direction.
EDIT
First exclude product from the formset
AttributeFormSet = modelformset_factory(Attribute, exclude=('product',))
then change the form processing block to not commit on save, and manually attach the product.
if formset.is_valid() :
for form in formset:
# get this form's instance
b = form.save(commit=False)
# attach product
b.product = product
# save the instance
b.save()
By using f = form.instance you access the original instance. If the attribute_form is valid you call the save method on the form, instead of the f. All the changes you did on f will be lost.
Have a look at saving-objects-in-the-formset how to update instances of a formset before saving them.