Flask WTForms FormField/FieldList prepopulate + submit - flask

For the sake of example, I'm making a cookbook app. Looking through this SO question here, I've managed to get a little of the way, but having difficulty conceiving how to prepopulate the form and submit the data.
Honestly the documentation is a little short on detail and lacking in examples, and most of the examples I've seen on the web are either lacking in interaction with the database, or just a little too complex.
For the sake of simplicity, say the data structure is 1 x Recipe -> Many Recipe Steps.
All I want to do is to be able to prepopulate the form with data from the db matching the recipe (I currently have the correct amount of steps displaying but no prepopulation), then when the form is updated, submit the amendments to the db. The SO answer I linked above, leaves the population/submission stuff out of this, and it's a little beyond Flask basics for me.
View.py
#app.route('/recipe/<int:recipe_id>', methods=['GET', 'POST'])
def recipe(recipe_id):
recipe_form = RecipeForm()
init_recipe_steps = [dict(recipestep=s.id, recipe_id=recipe_id) for s in RecipeSteps.query.filter_by(recipestep=recipe)]
recipe_step_form = RecipeStepFormSet(stepset=init_recipe_steps)
if request.method == 'GET':
# how do I get the info from the dictionary to show here?
recipe_step_form.recipe_step_name.data = recipe_step_form.recipe_step_name
recipe_step_form.recipe_step_temp.data = recipe_step_form.recipe_step_temp
Forms.py
class RecipeStepForm(FlaskForm):
recipe_step_name = StringField('Recipe Profile Name')
recipe_step_temp = DecimalField('Step Temp', places=1)
class RecipeStepFormSet(FlaskForm):
stepset = FieldList(FormField(RecipeStepForm), min_entries=0)
Template
{% for step in recipe_step_form.stepset %}
{{ step.recipe_step_name }}
{{ step.recipe_step_temp }}
{% endfor %}
Prepopulating the form Answer
init_recipe_steps = [dict(recipestep=s.id, recipe_id=recipe_id, recipe_step_name=s.name, recipe_step_temp=s.step_temp) for s in RecipeSteps.query.filter_by(recipestep=recipe)]
Turns out I was fairly close, but the dictionary wasn't defined properly so the data wasn't coming through. See above for the solution.

Related

Forms, Model, and updating with current user

I think this works, but I came across a couple of things before getting it to work that I want to understand better, so the question. It also looks like other people do this a variety of ways looking at other answers on stack overflow. What I am trying to avoid is having the user to have to select his username from the pulldown when creating a new search-profile. The search profile model is:
class Search_Profile(models.Model):
author_of_profile = models.ForeignKey(settings.AUTH_USER_MODEL,on_delete=models.CASCADE,blank=True)
keyword_string = models.CharField(max_length=200)
other_stuff = models.CharField(max_length=200)
The form I ended up with was:
class Search_Profile_Form(ModelForm):
class Meta:
model = Search_Profile
fields = [ 'keyword_string', 'other_stuff']
Where I deliberately left out 'author_of_profile' so that it wouldn't be shown or need to be validated. I tried hiding it, but then the form would not save to the model because a field hadn't been entered. If I was o.k. with a pulldown I guess I could have left it in.
I didn't have any issues with the HTML while testing for completeness:
<form action="" method="POST">
{% csrf_token %}
{{ form.author_of_profile}}
{{ form.keyword_string }}
{{ form.other_stuff }}
<input type="submit" value="Save and Return to Home Page">
</form>
And the View is where I ended up treating the form and the model separated, saving the form first, then updating the model, which is where I think there might be some other way people do it.
def New_Profile(request):
if request.method=='POST':
form = Search_Profile_Form(request.POST)
if form.is_valid():
post=form.save(commit=False)
# here is where I thought I could update the author of profile field somehow with USER
# but If I include the author_of_profile field in the form it seems I can't.
post.save()
#So instead I tried to update the author_of profile directly in the model
current_profile=Search_Profile.objects.last()
current_profile.author_of_profile=request.user
current_profile.save()
return(redirect('home'))
else:
form=Search_Profile_Form()
return render(request, 'mainapp/New_Profile.html', {'form': form})
So a few questions:
For the Foreign Key in author_of_profile field, is it better to use blank=True, or null=True
I ended up using request.user rather than importing from django.contrib.auth.models import User is there any difference?
My real question though, is the above a reasonable way to get form data and update the database with that data and the user? Or am I missing some other way that is more build in?
post=form.save()
current_profile.author_of_profile=request.user
post.save()
return(redirect('home'))
try something like this. save the form to db then change the author again. save(commit=False) will not save the date to db immediately.

Create a list of arrays using Django

Around one year ago I started to write my first application in PyQt5, where there are many fields:
I know it's not good-looking, but the main point was to learn PyQt5.
Now I want to make it useable on mobile. Since there are many people after IT studies knowing Java, I think I have a better chance to get the first job knowing Django + Javascript/jQuery. Here is the question:
Each "F1, F2, F3" is a separate widget. The screen on a phone is small, so I think about displaying them one by one. Easiest approach is probably this way (.html), but maybe there is a better one:
{% for field in GoldArrays %}
<p>{{field.text}} <input type="number" name="textfield"> {{field.number}}</p>
{% endfor %}
It's for F3/F4 widget. 'text' will return 200zl, 100zl, etc, number is just a DecimalField.
The thing is I'm very fresh to web development and Django. As I understand I should create a separate template for each widget. I want to collect data from a widget after pressing "next" button and then store it to SQLite database, so the progress will be saved. Should I put sql queries in views.py? What should be in forms.py and models.py? An example of two user inputs is highly appreaciated.
Here is the next question... There is a decent chance, that I'll want to set some rules for many fields, like highlighting value so the user doesn't need to delete the value, or to automatically set 0 in case there is None as input. I know I can do that with JS later, but can I do it with Django? I hope to write a code, that is easy to maintain.
Help please :)
Using views (controllers in Django) you can add context data.
It has next structure:
{% for item in form_set %}
{{ item }}
{% endfor %}
Jinja2 is templating language, so all vars from backend you must write in brackets
{{ each_var_from_django }}
View will be next:
class CreateOrderView(LoginRequiredMixin, CreateView):
template_name = 'dashboard/index.html'
success_url = reverse_lazy('namespace:url')
def get_context_data(self, *args, **kwargs):
ctx = super().get_context_data(**kwargs)
ctx['form_set'] = Model.objects.filter()
return ctx

Django Alter commandline filter to use in project

So since i've been trying to learn django there is one thing that confuses me more than anything else, and that is getting something that works in the django shell into a format that works in models.py of views.py. So here is an example from the Documentation:
>>> Entry.objects.all().filter(pub_date__year=2006)
So I can work with the shell, there are loads of examples everywhere, what never seems to be covered is how you then put this in your code if you want to filter for other years for example. Can someone explain this or point me at documentation that explains this, as I've not found it in the django docs.
There are several ways to do that, but for the beginning and learning purposes your view should accept a year argument:
def my_view(request, year):
entries = Entry.objects.filter(pub_date__year=year)
context = {
"entries": entries
}
return TemplateResponse(request, 'my_template.html', context)
The line you are talking about is used to query the database and then filter things. This line is mostly used in views or serializers since you want the entries to be passed on to either template or as JSON to API response.
Using it in views:
def view(request):
entries = Entry.objects.filter(pub_date__year=year)
return render(request, 'index.html', {'entries': entries})
Here the object {'entries': entries} in above code is context object.
And then after that, you can use the same thing in Django Templates for example here in index.html
{% for entry in entries %}
<li> {{ entry.id }} </li>
{% endfor %}

Restricting Access to Django's success_url()

New Django user here.
I am trying to restrict access to Django's success_url() upon GET requests. I realize I am not the first to ask this question, however, I am specifically trying to achieve this in conjunction with Django's generic class-based views. Generic views seem cleaner, faster, and more idiomatic. I want to use them as much as possible unless they are absolutely unfit for the job.
Basically, I am creating a simple contact form for non-users who only want to send me a message without creating an account. I've created a contact app to handle these types of contacts. I've created a ModelForm, which I am rendering with a contact.html with Django's FormView. After a person submits the form, they will receive a cool looking thank you message, rendered with a thanks.html, which has its own url.
But I only want them to see the thank you message if they POST the contact form. Currently, you can go on my site and type '/contact/thanks/', and my thanks.html will be rendered whether you've submitted a form or not. Django's success_url apparently defaults to a GET request.
Here's my view:
class MyContact(FormView):
template_name = 'contact.html'
form_class = ContactForm
success_url = 'thanks'
Here's my form:
ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = ['email_address', 'message_body']
Here's the html form in contact.html:
<form action="" method="POST">{% csrf_token %}
{{ form|crispy }}
<button type="submit" class="send btn btn-secondary">Send Message</button>
</form>
My first thought was to decorate my contact app url with a require_POST() decorator like this:
urlpatterns = [
url(r'^thanks/$', require_POST(views.ThanksPageView.as_view()), name='thanks'),
]
This doesn't work because Django's success_url() defaults to a GET request.
What is the most idiomatic way to approach this? Since Django is a 'batteries included' framework, I do not want to use 'duck-tape', meaning I do not want implement any ad-hoc logic in my views or urls.
Sorry if I've missed anything in the docs or questions archive.
Since you're asking for a idiomatic approach, I would consider the messages framework and specifically the SuccessMessageMixin. This would allow you to add a success message to e.g. the contact form itself and the url-pattern for the /thanks/ page would not be necessary.

Django - get an object back from template form to view (from a search result)

In my Django app, the user is presented a simple form, on validation the view searches the data (some web scraping) and returns a template in the context of which we added a list of the results, we present them to the user and he/she chooses one and (this is my problem) I want to get that choice back to another view (now we want to add the selected object to the database).
I identified my problem, but I really think I'm missing something and not using Django the right way. For now this is how I display the data and put it back in a form: (jade template. Of course I do this with many more attributes so the template is quite big and I have to add an hidden input for each value I want back)
for b in result_list
h4
for aut in b.authors
div {{ aut }}
form(action='/add/', method='post') {% csrf_token %}
{% csrf_token %}
input(type='hidden', value="{{ b.authors }}", name='authors')
edit2: there is one button per book. I am just trying to return one book.
It works fine when authors has a single element, but with more than once the template doesn't return a valid json. I could give you the structure but I tend to think this is not the point.
What am I missing ? How to display search results and get the selected one to another view ?
Do I miss something about forms ? Shall I use a session variable, a context processor with middleware ? I guess the hidden input is wrong: how can I do differently ?
many thanks
edit: below I show where my problem is. However, maybe I shouldn't fix that problem but use Django the right way instead.
1- my 1st view renders a template and feeds it with the search results:
return render(request, "search/search_result.jade", {
"return_list": book_list,
})
book_list is a list of dicts where authors is a list of unicode str:
retlist[0]["authors"]
>>> [u'Ayroles, Alain']
2- my template is above. It displays a form and a hidden input. The goal is to send back a book: a dictionnary with the title, the editor… and a list of authors.
3- now the users clicks "Add this book", we are in the "add" view. Let's inspect the authors:
req = request.POST.copy()
authors = req["authors"]
>>> u"[u'Ayroles']" # note the u after the [
# this is not a valid json, I can't get a list out of it
import json
json.loads(req["authors"])
>>> *** ValueError: No JSON object could be decoded
So I passed a list from the first view to the template, I parsed it and displayed it, but the template doesn't return a valid list/json to the second view in the POST parameters and I can't extract my authors. I don't know what to think:
else what I want to do is fine and I just have to better handle my data structure
else I shouldn't be doing like that and use more of Django: use more forms ? sessions ?
This is how I see it. It is not really an answer, but it's already too much code to put into comments
Template:
for b in result_list
h4
for aut in b.authors
div {{ aut }}
form(action='/add/', method='post')
{% csrf_token %}
input(type='hidden', value="{{ b.authors }}", name='authors')
input(type='submit', value="Add book")
View:
if request.method == 'POST':
authors = request.POST.get['authors']
#do something with authors, e.g. create a JSON string:
authors_JSON = json.dumps(authors)
Same logic, using Django forms:
View:
if request.method == 'POST':
book = BookForm(request.POST)
#do something with authors, e.g. create a JSON string:
authors_JSON = json.dumps(book.authors)
Forms.py:
class ContactForm(forms.Form):
authors = forms.CharField(max_length=100)
#etc, possibly including custom __init__ logic
That 'u' thing, it happens because you do json.loads() on an object. If you want to serialize, it should be json.dumps(), otherwise array converted to string and then treated as JSON, that is why Python unicode mark got there (i.e. not a template bug)
I finally got it: we can easily share data between views using the session.
The session is activated in the default Django install. We just have to choose a session engine: in a temporary file, in memory,…
SESSION_ENGINE = 'django.contrib.sessions.backends.file'
Now in my search/ url I can add a variable in request.session, a dictionnary-like object:
search_results = compute_and_get_data()
request = search_results()
I feed my template with the search_results list. In my template, I iterate on it and I use a single hidden input field to return the counter of the for loop.
for b in search_results
table
tr
td
h4 {{b.authors}}
form(action='/add/', method='post') {% csrf_token %}
{% csrf_token %}
input(type='hidden', value="{{ forloop.counter0 }}", name='forloop_counter0')
input.btn.btn-primary(type='submit', value='add book', title="add that book to my collection")
On submit, we enter the add/ view. Here I can get the book the user selected:
forloop_counter0 = int(request.POST["forloop_counter0"])
book = request.session["search_result"][forloop_counter0]
Now my book is a dictionnary with a lot of information. I didn't have to handle json formatting and the like.
This question helped me:
Django Passing data between views
How do you pass or share variables between django views?