I show two solutions, one with GET, the other with Javascript. In both I need to double click the back button to come back to the previous page. I think this is a Django problem.
GET case
In templates I have the following form:
<form id="form-user" action="" method="GET">
<select id="user" data-slug="{{x.user}}" onChange=selectChange(this)>
<option value="">choose</option>
<option value="view">View</option>
</select>
</form>
<script>
function selectChange(select) {
var selectID = select.id;
var value = select.value;
if ($('#'+selectID).data('slug')) {
var my_slug = $('#'+selectID).data('slug');
var my_url = "{% url 'profiles:profile-detail' slug=none %}".replace(/none/, my_slug.toString());
}
if (value == 'view'){
$("#form-"+selectID).attr("action", my_url);
$("#form-"+selectID).submit();
}
}
</script>
In views.py I have:
class ProfileView(DetailView):
model = Profile
def get_object(self, *args, **kwargs):
myslug = self.kwargs.get('slug')
user = User.objects.get(username=myslug)
profile = Profile.objects.get(user=user)
return profile
In urls.py I have:
path('<slug>/', views.ProfileView.as_view(), name='profile-detail'),
This approach takes me to the profile page of {{x.user}}=user1, however when I click the backward button in the browser, it goes from mypath/user1 to mypath/user1?. I don't like this ?. To go back to the previous page I would need to click the backward button twice. I would like to click it only once and remove this ? transition step.
Javascript case
views.py doesn't change as it is not needed. template is quite similar, we just remove the method="GET" in the HTML form tag, while in the javascript we only change the following:
if (value == 'view'){
window.location.href = my_url;
}
Here also, I manage to go to the user page, however to come back to the previous page I need a double click, in this case it goes from mypath/user1 to mypath/user1 and then to the previous page.
I do believe it is a django problem but I'm not sure if we can solve it
EDIT:
As suggested a request POST solution is not required in this case. Also views.py is better written as:
def get_object(self, *args, **kwargs):
slug = self.kwargs.get('slug')
return get_object_or_404(Profile, user__username=slug)
However this doesn't fix the problem.
What about just a link?
View
Through CSS, you can style the link as a button or something else. This will trigger a GET request. A POST request makes not much sense, since you do not change the state: GET requests are supposed to have no side-effects, and POST requests are normally used to create, update, or remove data.
Your view can also be simplified: fetching the User is not necessary:
from django.shortcuts import get_object_or_404
class ProfileView(DetailView):
model = Profile
def get_object(self, *args, **kwargs):
return get_object_or_404(Profile, user__username=myslug)
I am trying to render a page by filtering the articles by tags. I want the related articles to be displayed when a user clicks the tag in All-articles.html
Im not sure how to go about it or how I would write the code.
Should I create a path in urls.py like
path('tag/<str:tagslug>', views.tag, name='tag'),
so that I can access the url within the views.py?
How would I write the ORM to filter articles by tags in views.py?
Any help will be appreciated :) Thanks
models.py
class ArticleTags(models.Model):
article = models.ForeignKey('Articles', models.DO_NOTHING)
tag = models.ForeignKey('Tags', models.DO_NOTHING)
class Tags(models.Model):
tag = models.CharField(unique=True, max_length=75)
tagslug = models.SlugField(max_length=200, unique=True, default=None)
class Articles(models.Model):
title = models.CharField(max_length=155)
metatitle = models.CharField(max_length=155)
slug = models.SlugField(unique=True, max_length=155)
summary = models.TextField(blank=True, null=True)
field_created = models.DateTimeField(db_column='_created', blank=True, null=True)
cover = models.ImageField(upload_to="cover", blank=True, default='logo-00-06.png')
views.py
def allarticles(request):
articles_list = Articles.objects.all()
paginator = Paginator(articles_list, 12)
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {'page_obj': page_obj}
return render(request, "All-articles.html", context)
All-articles.html
{% for article in page_obj %}
<article>
<div>
<img
src="media/{{ article.cover }}"
alt="menu item"
class="article-photo"
/>
</div>
<header>
<h4>{{ article.title }}</h4>
</header>
</article>
{% endfor %}
You could begin by setting it up so when the user clicks the tag, it sends the tag name to the server via POST request. You could then filter the articles via the tag name and return the new context to the template.
The problem with this is you will have a page refresh unless you do it via AJAX or use e.preventdefault()
If you're going to have multiple actions on one page that requires HTTP requests, I would strongly recommend avoiding page refreshes.
When the user selects a tag, make an Ajax request like so:
All-Articles.js
function myAjaxRequest() {
return $.ajax({
type: 'POST',
url: 'your route to the function in views.py (if its the same page, this
will be an empty string)',
data: {filter: yourTag}
})
}
Then use the tag to filter your data and return via Json Response.
views.py
def allarticles(request):
if request.is_ajax():
tag = request.POST['filter']
*Now just use the tag to filter your model.*
data = serializers.serialize('json', MyModel.objects.filter(field_name=tag))
return JsonResponse(data, safe=False)
Finally, take the response and insert it into your HTML where it belongs. You will get the response from the return of your AJAX call in your Javascript file.
My simple web-application has two models that are linked (one to many).
The first model (Newplate) has a boolean field called plate_complete. This is set to False (0) at the start.
questions:
In a html page, I am trying to build a form and button that when pressed sets the above field to True. At the moment when I click the button the page refreshes but there is no change to the database (plate_complete is still False). How do I do this?
Ideally, once the button is pressed I would also like to re-direct the user to another webpage (readplates.html). This webpage does not require the pk field (but the form does to change the specific record) Hence for now I am just refreshing the extendingplates.html file. How do I do this too ?
My code:
"""Model"""
class NewPlate(models.Model):
plate_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
title = models.CharField(max_length=200)
created_date = models.DateTimeField(default=timezone.now)
plate_complete = models.BooleanField()
"""view"""
def publish_plates(request,plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
newplate.plate_complete = True
newplate.save()
#2nd method
NewPlate.objects.filter(pk=plate_id).update(plate_complete = True)
return HttpResponseRedirect(reverse('tablet:extendplates', args=[plate_id]))
"""URLS"""
path('readplates', views.read_plates, name='readplates'),
path('extendplates/<pk>/', views.show_plates, name='showplates'),
path('extendplates/<pk>/', views.publish_plates, name='publishplates'),
"""HTML"""
<form method="POST" action="{% url 'tablet:publishplates' newplate.plate_id %}">
{% csrf_token %}
<button type="submit" class="button" value='True'>Publish</button></form>
-------Added show plates view:---------
def show_plates(request,pk):
mod = NewPlate.objects.all()
newplate= get_object_or_404(mod, pk=pk)
add2plate= Add2Plate.objects.filter(Add2Plateid=pk)
return render(request, 'tablet/show_plates.html', {'newplate': newplate,'add2plate': add2plate})
Thank you
The problem is two of your urls have the same pattern 'extendplates/<pk>/'. Django uses the first pattern that matches a url. I suppose that one of these view views.show_plates is meant to display the form and the other views.publish_plates is meant to accept the posted form data.
This means that simply both of these views should simply be a single view (to differentiate if the form is submitted we will simply check the requests method):
from django.shortcuts import redirect, render
def show_plates(request, plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
if request.method == "POST":
newplate.plate_complete = True
newplate.save()
return redirect('tablet:extendplates', plate_id)
context = {'newplate': newplate}
return render(request, 'your_template_name.html', context)
Now your url patterns can simply be (Note: Also captured arguments are passed as keyword arguments to the view so they should be consistent for your view and pattern):
urlpatterns = [
...
path('readplates', views.read_plates, name='readplates'),
path('extendplates/<uuid:plate_id>/', views.show_plates, name='showplates'),
...
]
In your form simply forego the action attribute as it is on the same page:
<form method="POST">
{% csrf_token %}
<button type="submit" class="button" value='True'>Publish</button>
</form>
You should avoid changing state on a get request like your view does currently.
Handle the POST request and change the data if the request is valid (ensuring CSRF protection).
def publish_plates(request,plate_id):
newplate = get_object_or_404(NewPlate, pk=plate_id)
if request.method == "POST":
newplate.plate_complete = True
newplate.save(update_fields=['plate_complete']) # a more efficient save
#2nd method
NewPlate.objects.filter(pk=plate_id).update(plate_complete=True)
return HttpResponseRedirect(reverse('tablet:extendplates', args=[plate_id]))
You could also put a hidden input in the form, or make a form in Django to hold the hidden input, which stores the plate_id value and that way you can have a generic URL which will fetch that ID from the POST data.
Now the real problem you've got here, is that you've got 2 URLs which are the same, but with 2 different views.
I'd suggest you change that so that URLs are unique;
path('extendplates/<pk>/', views.show_plates, name='showplates'),
path('publish-plates/<pk>/', views.publish_plates, name='publishplates'),
Intro: I have a python Django web app where users are allowed to create posts. Each post has 1 main image and followed by extra images (max 12 & min 2) that are associated with that post. I want to let users add a total of 13 images. 1 main image and 12 extra images.
The issue: Usually users take photos with their smart phones. which makes image size upto 10MB . with 13 images that can become 130MB form. My django server can accept a max of 10MB form. So I cannot reduce the images ServerSide
What I want to do: I want such that when the user uploads each image to a form. The size of that image is reduced on client side and it is asynchronously saved in a temporary place on my server using Ajax. When the post is created all these images are linked to the post. So basically when the user hits submit on the post create form. Its a super light form with no images. Sounds too ambitious.. ha ha maybe
What I have so far:
I have the models/views (all django parts that create a post) without the asynchronous part. As in, if the form after all images are added is less than 10MB. My post is created with how many ever extra images
I have the Javascript code that reduces the size of the images on the client side and asynchronously adds it to my server. All I need to do is give it a endpoint, which is a simple url
I have a rough idea of how I plan to achieve this
Now to show you my code
My Models (Just the django part no asynchronous part added as yet)
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts')
title = models.CharField(max_length=250, unique=True)
slug = models.SlugField(allow_unicode=True, unique=True, max_length=500)
message = models.TextField()
post_image = models.ImageField()
class Extra (models.Model): #(Images)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='post_extra')
image = models.ImageField(upload_to='images/', blank=True, null=True, default='')
image_title = models.CharField(max_length=100, default='')
image_description = models.CharField(max_length=250, default='')
sequence = models.SmallIntegerField(validators=[MaxValueValidator(12), MinValueValidator(1)])
My views (Just the django part no asynchronous part added as yet)
#login_required
def post_create(request):
ImageFormSet = modelformset_factory(Extra, fields=('image', 'image_title', 'image_description'), extra=12, max_num=12,
min_num=2)
if request.method == "POST":
form = PostForm(request.POST or None, request.FILES or None)
formset = ImageFormSet(request.POST or None, request.FILES or None)
if form.is_valid() and formset.is_valid():
instance = form.save(commit=False)
instance.user = request.user
instance.save()
for index, f in enumerate(formset.cleaned_data):
try:
photo = Extra(sequence=index+1, post=instance, image=f['image'],
image_title=f['image_title'], image_description=f['image_description'])
photo.save()
except Exception as e:
break
return redirect('posts:single', username=instance.user.username, slug=instance.slug)
Now Just to keep things simple I will not add any Javascript in this question. Adding the below script tag to my form makes the image saved asynchronously to the server. You can read more about Filepond if you wish
'''See the urls below to see where the **new_image** is coming from'''
FilePond.setOptions({ server: "new_image/",
headers: {"X-CSRF-Token": "{% csrf_token %}"}}
}); #I need to figure how to pass the csrf to this request Currently this is throwing error
My plan to make it work
Add a new model below the existing 2 models
class ReducedImages(models.Model):
image = models.ImageField()
post = models.ForeignKey(Post, blank=True, null=True, upload_to='reduced_post_images/')
Change the view as below (only working on the main image for now. Not sure how to get the Extra images )
''' This could be my asynchronous code '''
#login_required
def post_image_create(request, post):
image = ReducedImages.objects.create(image=request.FILES)
image.save()
if post:
post.post_image = image
#login_required
def post_create(request):
ImageFormSet = modelformset_factory(Extra, fields=('image', 'image_title', 'image_description'), extra=12, max_num=12,
min_num=2)
if request.method == "POST":
form = PostForm(request.POST or None)
formset = ImageFormSet(request.POST or None, request.FILES or None)
if form.is_valid() and formset.is_valid():
instance = form.save(commit=False)
instance.user = request.user
post_image_create(request=request, post=instance) #This function is defined above
instance.save()
for index, f in enumerate(formset.cleaned_data):
try:
photo = Extra(sequence=index+1, post=instance, image=f['image'],
image_title=f['image_title'], image_description=f['image_description'])
photo.save()
except Exception as e:
break
return redirect('posts:single', username=instance.user.username, slug=instance.slug)
else:
form = PostForm()
formset = ImageFormSet(queryset=Extra.objects.none())
context = {
'form': form,
'formset': formset,
}
return render(request, 'posts/post_form.html', context)
my urls.py
url(r'^new_image/$', views.post_image_create, name='new_image'),
Any suggestions on how I can make this work
My Templates
{% extends 'posts/post_base.html' %}
{% load bootstrap3 %}
{% load staticfiles %}
{% block postcontent %}
<head>
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet" type="text/css"/>
<link href="https://unpkg.com/filepond-plugin-image-edit/dist/filepond-plugin-image-edit.css" rel="stylesheet" type="text/css"/>
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css" rel="stylesheet" type="text/css"/>
<link href="{% static 'doka.min.css' %}" rel="stylesheet" type="text/css"/>
<style>
html {
font-family: -apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;
font-size: 1em;
}
body {
padding: 2em;
max-width: 30em;
}
</style>
</head>
<body>
<div class="container">
<h2> Add a new Recipe</h2>
<form action="" method="post" enctype="multipart/form-data" id="form">
{% csrf_token %}
{% bootstrap_form form %}
<img alt="" id="preview" src="" width="100" />
<img alt="" id="new_image" src="" style="display: none;" />
{{formset.management_form}}
<h3 class="text-danger">You must be present in at least 1 image making the dish. With your face clearly visible and
matching your profile picture
</h3>
<h5>(Remember a picture is worth a thousand words) try to add as many extra images as possible
<span class="text-danger"><b>(Minimum 2)</b></span>.
People love to see how its made. Try not to add terms/language which only a few people understand.
Please add your own images. The ones you took while making the dish. Do not copy images</h5>
{% for f in formset %}
<div style="border-style: inset; padding:20px; display: none;" id="form{{forloop.counter}}" >
<p class="text-warning">Extra Image {{forloop.counter}}</p>
{% bootstrap_form f %}
<img alt="" src="" width="60" id="extra_image{{forloop.counter}}" />
</div>
{% endfor %}
<br/><button type="button" id="add_more" onclick="myFunction()">Add more images</button>
<input type="submit" class="btn btn-primary" value="Post" style="float:right;"/>
</form>
</div>
<script>
[
{supported: 'Promise' in window, fill: 'https://cdn.jsdelivr.net/npm/promise-polyfill#8/dist/polyfill.min.js'},
{supported: 'fetch' in window, fill: 'https://cdn.jsdelivr.net/npm/fetch-polyfill#0.8.2/fetch.min.js'},
{supported: 'CustomEvent' in window && 'log10' in Math && 'sign' in Math && 'assign' in Object && 'from' in Array &&
['find', 'findIndex', 'includes'].reduce(function(previous, prop) { return (prop in Array.prototype) ? previous : false; }, true), fill: 'doka.polyfill.min.js'}
].forEach(function(p) {
if (p.supported) return;
document.write('<script src="' + p.fill + '"><\/script>');
});
</script>
<script src="https://unpkg.com/filepond-plugin-image-edit"></script>
<script src="https://unpkg.com/filepond-plugin-image-preview"></script>
<script src="https://unpkg.com/filepond-plugin-image-exif-orientation"></script>
<script src="https://unpkg.com/filepond-plugin-image-crop"></script>
<script src="https://unpkg.com/filepond-plugin-image-resize"></script>
<script src="https://unpkg.com/filepond-plugin-image-transform"></script>
<script src="https://unpkg.com/filepond"></script>
<script src="{% static 'doka.min.js' %}"></script>
<script>
FilePond.registerPlugin(
FilePondPluginImageExifOrientation,
FilePondPluginImagePreview,
FilePondPluginImageCrop,
FilePondPluginImageResize,
FilePondPluginImageTransform,
FilePondPluginImageEdit
);
// Below is my failed attempt to tackle the csrf issue
const csrftoken = $("[name=csrfmiddlewaretoken]").val();
FilePond.setOptions({
server: {
url: 'http://127.0.0.1:8000',
process: {
url: 'new_image/',
method: 'POST',
withCredentials: false,
headers: {
headers:{
"X-CSRFToken": csrftoken
},
timeout: 7000,
onload: null,
onerror: null,
ondata: null
}
}
}});
// This is the expanded version of the Javascript code that uploads the image
FilePond.create(document.querySelector('input[type="file"]'), {
// configure Doka
imageEditEditor: Doka.create({
cropAspectRatioOptions: [
{
label: 'Free',
value: null
}
]
})
});
The below codes are exacty like the one above. I have just minimised it
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
FilePond.create(document.querySelector('input[type="file"]'), {...});
// ignore this part This is just to have a new form appear when the add more image button is pressed. Default is 3 images
<script>
document.getElementById("form1").style.display = "block";
document.getElementById("form2").style.display = "block";
document.getElementById("form3").style.display = "block";
let x = 0;
let i = 4;
function myFunction() {
if( x < 13) {
x = i ++
}
document.getElementById("form"+x+"").style.display = "block";
}
</script>
</body>
{% endblock %}
I have not added the forms.py as they were not relevant
According to your problem there are four things to do.
Make temporary files storage tracker.
Uploading files immediately after user selects image (Somewhere on the storage may be temporary location) server responds with link of reduced image.
When user Posts form that pass just references to those images then save Post with given references.
Handle temporary location efficiently. (By some batch processing or some celery tasks.)
Solution
1. Make temporary files storage tracker for files those are uploaded asynchronously.
Your temporary uploaded files will be stored in TemporaryImage model in the temp_folder as following structure.
Update your models.py
models.py
class TemporaryImage(models.Model):
image = models.ImageField(upload_to="temp_folder/")
reduced_image = models.ImageField(upload_to="temp_thumb_folder/")
image_title = models.CharField(max_length=100, default='')
image_description = models.CharField(max_length=250, default='')
sequence = models.SmallIntegerField(validators=[MaxValueValidator(12), MinValueValidator(1)])
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts')
title = models.CharField(max_length=250, unique=True)
slug = models.SlugField(allow_unicode=True, unique=True, max_length=500)
message = models.TextField()
post_image = models.ImageField()
class Extra (models.Model): #(Images)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='post_extra')
image = models.ImageField(upload_to='images/', blank=True, null=True, default='')
image_thumbnail = models.ImageField(upload_to='images/', blank=True, null=True, default='')
image_title = models.CharField(max_length=100, default='')
image_description = models.CharField(max_length=250, default='')
sequence = models.SmallIntegerField(validators=[MaxValueValidator(12), MinValueValidator(1)])
Here TemporaryImage contains temporary uploaded files the field raw_image represent original uploaded file and reduced_image stands for thumbnails which is generated after file upload.
In order to send asynchronous java script request you need to install django-restframewrok by following command.
pip install djangorestframework
After installing restframework add serializers.py with following code.
serializers.py
from rest_framework import serializers
class TemporaryImageUploadSerializer(serializers.ModelSerializer):
class Meta:
model = TemporaryImage
field = ('id', 'image',)
def create(self, validated_data):
raw_image = validated_data['raw_image']
# Generate raw image's thumbnail here
thumbnail = generate_thumbnail(raw_image)
validated_data['reduced_image'] = thumbnail
return super(TemporaryImageUploadSerializer, self).create(validated_data)
This serializer generates thumbnail when user asynchronously uploads file. generate_thumbnail function will do this job. Implementation of this method can be found from here.
Add this serializer in viewset as below
apis.py
from rest_framework.generics import CreateAPIView, DestroyAPIView
from .serializers import TemporaryImageUploadSerializer
# This api view is used to create model entry for temporary uploaded file
class TemporaryImageUploadView(CreateAPIView):
serializer_class = TemporaryImageUploadSerializer
queryset = TemporaryImage.objects.all()
class TemporaryImageDeleteView(DestroyAPIView):
lookup_field = 'id'
serializer_class = TemporaryImageUploadSerializer
queryset = TemporaryImage.objects.all()
This TemporaryImageUploadViewSet creates POST, PUT, PATCH, DELETE methods for your uploads.
Update your urls.py as below
urls.py
from .apis import TemporaryImageUploadView, TemporaryImageDeleteView
urlpatterns = [
...
url(r'^ajax/temp_upload/$', TemporaryImageUploadView.as_view()),
url(r'^ajax/temp_upload/(?P<user_uuid>[0-9]+)/$', TemporaryImageDeleteView.as_view()),
...
]
This will create following endpoints for to handle asynchronous uploads
<domain>/ajax/temp_upload/ POST
<domain>/ajax/temp_upload/{id}/ DELETE
Now those endpoints are ready to handle file uploads
2. Uploading files immediately after user selects image
For this you need to update your template.py to handle iamge uploads when user select extra images and post with image field upload this to <domain>/ajax/temp_upload/ with POST method this would return you following sample json data.
{
"id": 12,
"image": "/media/temp_folder/image12.jpg",
"reduced_image": "/media/temp_thumb_folder/image12.jpg",
}
You can preview image from reduced_image key inside json.
id is reference for your temporary uploaded file you need to store it somewhere to pass in the Post create form. i.e As hidden field.
I am not writing javascript code because answer will become more lengthy.
3. When user Posts form that pass just references to those images.
The uploaded files' id is set as Hidden field on formset in the HTML page. In order to handle formset you need to do following.
forms.py
from django import forms
class TempFileForm(forms.ModelForm):
id = forms.HiddenInput()
class Meta:
model = TemporaryImage
fields = ('id',)
def clean(self):
cleaned_data = super().clean()
temp_id = cleaned_data.get("id")
if temp_id and not TemporaryImage.objects.filter(id=temp_id).first():
raise forms.ValidationError("Can not find valida temp file")
This is single uploaded temporary file form.
You could handle this by using formset in django as below
forms.py
from django.core.files.base import ContentFile
#login_required
def post_create(request):
ImageFormSet = formset_factory(TempFileForm, extra=12, max_num=12,
min_num=2)
if request.method == "POST":
form = PostForm(request.POST or None)
formset = ImageFormSet(request.POST or None, request.FILES or None)
if form.is_valid() and formset.is_valid():
instance = form.save(commit=False)
instance.user = request.user
post_image_create(request=request, post=instance) #This function is defined above
instance.save()
for index, f in enumerate(formset.cleaned_data):
try:
temp_photo = TemporaryImage.objects.get(id=f['id'])
photo = Extra(sequence=index+1, post=instance,
image_title=f['image_title'], image_description=f['image_description'])
photo.image.save(ContentFile(temp_photo.image.name,temp_photo.image.file.read()))
# remove temporary stored file
temp_photo.image.file.close()
temp_photo.delete()
photo.save()
except Exception as e:
break
return redirect('posts:single', username=instance.user.username, slug=instance.slug)
else:
form = PostForm()
formset = ImageFormSet(queryset=Extra.objects.none())
context = {
'form': form,
'formset': formset,
}
return render(request, 'posts/post_form.html', context)
This would save Post with given references(temporary uploaded files).
4. Handle temporary location efficiently.
You need to handle temp_folder and temp_thumb_folder to keep your file system clean.
Suppose user uploads file and does not submit post form than you need to remove that files.
I know the answer became too lengthy to read, apologise for that yet edit this post if any improvements
Refer https://medium.com/zeitcode/asynchronous-file-uploads-with-django-forms-b741720dc952 for post related to this
Below is the answer I feel may be simpler to solve the above problem
How I got this idea
I wanted to send someone a email. I clicked compose I did not type anything. I got distracted by something and accidentally closed the browser. When I opened the email again. I saw there was a draft. It did not have anything in it. I was like Eureka!
What does a email have
sender = (models.ForeignKey(User))
receiver = models.ForeignKey(User
subject = models.CharField()
message = models.TextFied()
created_at = models.DateTimefield()
#Lets assume that Multiple attachments are like my model above.
Now the thing to notice is when I clicked compose and closed the window. It had only 2 of the above attributes
sender = request.user
created_at = timezone.now()
It created the email object with just these 2 things. So all the remaining attributes were optional. Also it saved it as a Draft hence there was another attribute called
is_draft = models.BooleanField(default=True)
I am sorry I have typed so much stuff and I still haven't got to the point(I have been watching a lot of court room drama. Its all relevant)
Now Lets apply all this to my problem.(I am sure some of you have already guessed the solution)
My models
'''I have made a lot of attributes optional'''
class Post(models.Model):
user = models.ForeignKey(User, related_name='posts') #required
title = models.CharField(max_length=250, unique=True, blank=True, null=True,) #optional
slug = models.SlugField(allow_unicode=True, unique=True, max_length=500, blank=True, null=True,) #optional
message = models.TextField(blank=True, null=True,) #optional
post_image = models.ImageField(blank=True, null=True,) #optional
created_at = models.DateTimeField(auto_now_add=True) #auto-genetrated
is_draft = models.BooleanField(default=True) #I just added this new field
class Extra (models.Model): #(Images)
post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='post_extra') #This is required
image = models.ImageField(upload_to='images/', blank=True, null=True, default='') #optional
image_title = models.CharField(max_length=100, default='') #optional
image_description = models.CharField(max_length=250, default='') #optional
sequence = models.SmallIntegerField(validators=[MaxValueValidator(12), MinValueValidator(1)]) #optional
Now in my code above the only thing needed to create this post is a logged_in user
I created a tab on my navbar called Drafts
Before: When the user clicked on add a post. A Blank form was rendered. which the user filled and when all the requirements were satisfied the post object was created. The create_post function above managed the view for creation of this post
Now: When the user clicks add a post. A post is created immediately and the Blank form that the user now sees is the post_edit form. I am adding Javascript barriers to stop the form from submitting unless all my previously required fields are satisfied.
The images are added asynchronously from my post_edit form. They are not orphaned images anymore. I don't need another model like previously to temporarily save the images. when the user adds the images they will get send one by one to the server. If everything is done properly. After all the images are added asynchronously. The user submits a super light form when he/she clicks submit. If the user abandons the form It is left on the users Navbar as Draft(1). You can let the user delete this draft. if he does not need it. Or have a simple code like
delete the draft after1 week if its still a draft. YOu can add this at user sign in
if post.is_draft and post.created_at > date__gt=datetime.date.today() + datetime.timedelta(days=6)
I will try and make a github code for exact execution with the javascript components.
Please let me know what you think of this approach. how I can better this. Or ask me doubts if something is not clear
I have a SelectField that is populated from a database table. I load the choices into the form as follows:
#statuses.route('/new', methods=['GET', 'POST'])
#login_required
def new_status():
form = StatusForm()
form.status_cd.choices = [(a.id, a.status_cd) for a in \
Status_Code.query.order_by('status_cd')]
if form.validate_on_submit():
status = Status(author=current_user)
form.to_model(status)
db.session.add(status)
db.session.commit()
flash('The status was added successfully.')
return redirect(url_for('.index'))
return render_template('statuses/new_status.html', form=form)
The model referenced in the query is as follows:
class Status_Code(db.Model):
__tablename__ = 'status_cd'
id = db.Column(db.Integer, primary_key=True)
status_cd = db.Column(db.String(16), nullable=False)
status_detail = db.Column(db.Text)
is_option_active = db.Boolean()
date_created = db.Column(db.DateTime, default=db.func.now())
And the form class is as follows:
class StatusForm(Form):
datetime = DateTimeField('Date / Time')
name = StringField('Name', validators=[Required()])
status_cd = SelectField('Status Code', coerce=int)
status_detail = TextAreaField('Status Detail', default="Default text",\
validators=[Required()])
submit = SubmitField('Submit')
The Question
Depending on the option selected in the SelectField, I want it to dynamically set the status_detail TextAreaField's default text. The value from the SelectField should query the database and return the status_detail, which should be the default text.
For example, if I have:
id status_cd status_detail
1 Apple The apples are red.
2 Banana The bananas are yellow.
If the user selects the "Apple" option, the default text should be "The apples are red."
Any assistance would be greatly appreciated! I am building my first Flask app so I'm new to this.
In my experience with flask, you can do this a couple of ways. There is no right way, and it is all up to you:
You can load the status_detail data, and place it in a data-detail tag in your select option value:
<select name='status_cd' onchange="get_status(this);">
{% for s in status %}
<option value='{{ s.id }}' data-detail='{{ s.status_detail }}'>{{ s.status_cd }} </option>
{% endfor %}
</select>
Then you can do an onchange with JavaScript, which can then get the data-detail value and update your text box (this is pseudo code, not meant for copy and paste):
<script>
function onchange(o){
var value = $(o).attr('data-detail');
//do something with the value
}
</script>
OR
You can do it where it pulls from the database dynamically if you don't wan to put the data-detail tag in your code, like this:
Same onchange with JavaScript, but can then do a call to an Ajax call to your routed method to return your value (this is pseudo code, not meant for copy and paste):
<script>
function onchange(o){
var value = $(o).value();
$.ajax({
url: '/my_rout',
data: value,
success : function(data){
//handle your result here
}
})
}
</script>
I hope this at least gets you in the right direction with some different options to choose from.