I want to access struct block default ID in its template - python

I want to save stream field ID into it's template.
In short, in text_question.html I am giving id = {{ self.id }} but that return Nothing.
I want this because in question.html file I want it to compare with {{
field.id }} which return stream field ID
In another word, I want to store {{ field.id }}'s value in id field of text_question.html
models.py
class TextQuestionBlock(blocks.StructBlock):
"""Text Question"""
question = blocks.CharBlock(required=True, help_text="Add your Question")
is_save = blocks.BooleanBlock(label="Want to save this field ?", required=False)
is_email = blocks.BooleanBlock(label="Want to get this field as an email ?", required=False)
class Meta: # noqa
template = "question/question_field/text_question.html"
icon = "edit"
label = "Text Question"
#register_setting(icon='fa-commenting')
class QuestionSettings(BaseSetting):
body = StreamField([
("text_question", TextQuestionBlock()),
], verbose_name='Question', blank=True)
panels = [
StreamFieldPanel('body')
]
class Meta:
verbose_name_plural = 'Question'
verbose_name = 'Questions'
text_question.html
{% load tag_library %}
<input issave="{{self.is_save}}" isemail="{{ self.is_email }}" class="text_question" type="text" name="{{ self.question|to_name }}" id="{{ self.id }}" data-conv-question="{{ self.question }}"
question.html
<form name="question_form" action="" method="post" class="hidden">
<div id="unique_id"></div>
{% for field in question.body %}
{{ field.id }}
{% endfor %}
<input type="text" data-conv-question="test">
</form>
Thank You!!!

The ID is not a built-in property of the block value - rather, it's a mechanism used by the StreamField container to keep track of its contents. It's not always meaningful for a block value to have an ID property: for example, the value of a CharBlock is a string, and you can't really have an .id property on a string. Similarly, child blocks of StructBlock won't be given one.
As a result, the id is not automatically available on a block's template - if you want it, you need to pass it explicitly from the calling template, via the {% include_block %} template tag. For example:
{% for field in question.body %}
{% if field.block_type == 'text_question' %}
{% include_block field with block_id=field.id %}
{% endif %}
{% endfor %}
This will make the ID available on text_question.html as the variable block_id.

Related

Django - Two forms on one page, how can I maintain URL parameters when either form is submitted?

I'm building an application that contains a list of meals, where each meal has various filters, a price, and a rating.
The filters are like tags; the user can select multiple, and the page only shows the meals that have the selected filters.
The price and ratings are integers, and the user can sort by either price or rating, which sorts the meals (cheapest -> most expensive for price, highest -> lowest for rating).
I have built two forms in Django, one for filters and one for sorting, and they both work on their own. However, let's say I submit the sorting form to sort by price; when I do this, it does sort by price, but it removes all of the prior filters I had submitted.
Below are the important pieces of code relevant to this problem:
views.py
def meals(request):
meal_list = Meal.objects.all()
tags = Tag.objects.all()
reviews = Review.objects.all()
filter_form = FilterForm(request.GET or None)
sorting_form = SortingForm(request.GET or None)
sort = ""
active_filters = []
if filter_form.is_valid():
tags = filter_form.cleaned_data.get('tags')
for tag in tags:
meal_list = meal_list.filter(tags__name=tag)
active_filters.append(tag)
if sorting_form.is_valid():
sort = sorting_form.cleaned_data.get('sort')
if sort == "price":
meal_list = meal_list.order_by('price')
else:
meal_list = meal_list.order_by('-rating')
paginator = Paginator(meal_list, 8)
page_number = request.GET.get('page')
meals_on_page = paginator.get_page(page_number)
context = {"meal_list": meal_list,
"distances": distances,
"tags": tags,
"reviews": reviews,
"active_filters": active_filters,
"meals_on_page": meals_on_page,
"filter_form": filter_form,
"sorting_form": sorting_form,
}
return render(request, 'meals/meals.html', context)
forms.py
from django import forms
# Tag is the model for the filters, it is just a ManyToManyField that contains a name attribute
from .models import Tag
class FilterForm(forms.Form):
tags = forms.ModelMultipleChoiceField(
queryset=Tag.objects.all(), widget=forms.CheckboxSelectMultiple)
class SortingForm(forms.Form):
SORT_CHOICES = [
('price', 'Price'),
('rating', 'Rating'),
]
sort = forms.ChoiceField(choices=SORT_CHOICES, widget=forms.Select)
meals.html
<form method="get">
{% for field in filter_form %}
{{ field.as_widget }} {{ field.label_tag }}
{% endfor %}
<input type="submit" value="Filter">
</form>
<form method="get">
{% for field in sorting_form %}
{{ field.as_widget }}
{% endfor %}
<input type="submit" value="Sort">
</form>
I have sadly way too long trying to fix this, and the closest I got was using get_copy = request.GET.copy() and then trying to manually add the URL parameters back onto the end of a URL after a form was submitted. However, none of my approaches using this seemed to work.
Thanks in advance for the help!
In your Django view, you can access the current URL parameters using the request object's GET attribute. To maintain these parameters when either form is submitted, you can include them in the form action attribute in your template.
For example, in your template, you can update the form action attribute to include the current URL parameters like this:
<form method="get" action="{% url 'meals' %}?{{ request.GET.urlencode }}">
This will append the current URL parameters to the form action, so when the form is submitted, the current parameters will be included in the request.
In your view, you can then access these parameters using the request.GET dictionary. You can use these parameters to filter and sort your queryset accordingly before rendering the template.
Note: you should also check if the forms are valid before processing the form data to avoid unexpected behavior.
Also, you can use Django's forms.HiddenInput() to include the current parameters on your forms as hidden fields, that way you don't need to update form's action attribute.
I ended up solving this after soaking (way too many) more hours into it.
The way I did this was to build my own dictionary of parameters, and then to pass these parameters into the forms as hidden inputs.
Below is the added code:
views.py
get_copy = request.GET.copy()
parameters = get_copy.urlencode()
get_copy.pop('page', None)
param_list = parameters.split("&")
param_dict = defaultdict(list)
for param in param_list:
try:
key, value = param.split("=")
except ValueError:
# Handle the case where there's no "=" in the parameter string
key = param
value = ""
if key == "tags":
param_dict[key].append(int(value))
else:
param_dict[key].append(value)
# Django requires me to turn dictionary to items here, rather than in the template
param_dict_items = param_dict.items()
meals.html
<form method="get" action="{% url 'meals' %}?{{ request.GET.urlencode }}">
{% for field in filter_form %}
{{ field.as_widget }} {{ field.label_tag }}
{% endfor %}
{% for key, value_list in param_dict_items %}
{% if key != 'tags' %}
{% for value in value_list %}
<input type="hidden" name="{{ key }}" value="{{ value }}">
{% endfor %}
{% endif %}
{% endfor %}
<input type="submit" value="Filter">
<form method="get" action="{% url 'meals' %}">
{% for field in sorting_form %}
{{ field.as_widget }}
{% endfor %}
{% for key, value_list in param_dict_items %}
{% if key != 'sort' %}
{% for value in value_list %}
<input type="hidden" name="{{ key }}" value="{{ value }}">
{% endfor %}
{% endif %}
{% endfor %}
<input type="submit" value="Sort">

How do you iterate through two items in a model and display on site using Django?

I have a models.py with the following fields:
class ChatStream(models.Model):
bot = models.TextField()
user = models.TextField()
name = models.CharField(max_length=100, null=True)
created_date = models.DateTimeField(auto_now_add=True)
And I'd like on a website to iterate through "bot" and "user" one at a time, so the site would hypothetically display something like:
bot: hello!
user: what's up?
bot: I'm good
user: What's your name
bot: bot is my name
.... etc. this would keep going...
So in my views.py I have
def displayDict(request):
m = ChatStream.objects.all()
return render(request, 'chatStream.html',
{"chat": m})
def send(request):
message = request.POST.get('userMessage', False)
ip = visitor_ip_address(request)
response = routes(message, ip)
print(ip, "user sent:", message, "bot response:", response)
chatItem = ChatStream(bot=response, user=message, name=ip)
chatItem.save()
return HttpResponseRedirect('/chat/')
Then in my template, chat.html I have
{% block chatStream %} {% endblock %}
And chatStream.html (this is where the error is happening I believe... how do you iterate through two items in the model so they appear one after the other on the html file?)
{% extends 'chat.html' %}
{% block chatStream %}
{% for a in bot%}
{% for b in user%}
<p>
<b>bot:</b> {{a}} <br>
<b>user:</b> {{b}} <br>
</p>
{% endfor %}
<form action="/send/" method = "post">{% csrf_token %}
<input type="text" name="userMessage">
<input type="submit" value="Send to smallest_steps bot">
</form>
{% endblock %}
But this does not work -- no text from the model is displayed on the site. I am not understanding how to iterate through two items within the model at once inside of the chatStream.html.
A lot going on here, lets try to break it down:
First, you need to pass context variables to your templates if you want to render them using the (jinja-like) Django template rendering system.
Your view function for rendering the template would look like this:
views.py
def render_chat_page(request):
# do some logic:
...
# pack the context variables:
context = {
'some_key' : 'some_value',
'chat_streams' : ChatStream.objects.all(),
...
}
return render(request, 'chat_page.html', context=context)
Ok, now that we've passed the context variables to the template, we can render html elements using the variables like so:
template.html
<div> The value of "some_key" is: {{some_key}} </div>
{% for chat_stream in chat_streams %}
<div> user says: {{chat_stream.user}}</div>
<div> bot says: {{chat_stream.bot}}</div>
{% endfor %}
This will render the user and bot messages for each ChatStream object. However my hunch is that this is not entirely what you're after, instead you may want something more dynamic.
In your displayDict view you're passing a QuerySet to the context. So, you need to loop over the QuerySet in your template.
{% extends 'chat.html' %}
{% block chatStream %}
{% for item in chat %}
<p>
<b>bot:</b> {{item.bot}} <br>
<b>user:</b> {{item.user}} <br>
</p>
{% endfor %}
<form action="/send/" method = "post">{% csrf_token %}
<input type="text" name="userMessage">
<input type="submit" value="Send to smallest_steps bot">
</form>
{% endblock %}

Manually rendered Django formset redirection problem at submition

I have the following models defined:
class Item(models.Model):
rfid_tag = models.CharField()
asset = models.OneToOneField('Assets', default=None, null=True,
on_delete=models.SET_DEFAULT,)
date = models.DateTimeField(name='timestamp',
auto_now_add=True,)
...
class Assets(models.Model):
id = models.AutoField(db_column='Id', primary_key=True)
assettag = models.CharField(db_column='AssetTag', unique=True, max_length=10)
assettype = models.CharField(db_column='AssetType', max_length=150)
...
class Meta:
managed = False
db_table = 'Assets'
ordering = ['assettag']
def __str__(self):
return f"{self.assettag}"
def __unicode__(self):
return f"{self.assettag}"
For which I have created the following form and formset:
class ItemDeleteForm(forms.ModelForm):
asset = forms.CharField(required=True,
help_text= "Item asset tag",
max_length=16,
label="AssetTag",
disabled=True,
)
delete = forms.BooleanField(required=False,
label="Delete",
help_text='Check this box to delete the corresponding item',
)
class Meta:
model = Item
fields = ['asset']
ItemDeleteMultiple = forms.modelformset_factory(model=Item,
form=ItemDeleteForm,
extra=0,
)
managed by the view:
class DeleteMultipleView(generic.FormView):
template_name = '*some html file*'
form_class = ItemDeleteMultiple
success_url = reverse_lazy('*app_name:url_name*')
def form_valid(self, form):
return super().form_valid(form)
And rendered in the template:
{% extends "pages/base.html" %}
{% block title %}
<title>Delete Multiple</title>
{% endblock %}
{% block content %}
<h1>Delete Multiple Items</h1>
<br>
<form class="ManualForm" action ="." method="POST"> {% csrf_token %}
{{ form.management_form }}
<table border="2">
<tr><th colspan="3" scope="row">Select Items to Delete</th></tr>
{% for item_form in form %}
<tr>
<td><label for="{{ item_form.asset.id_for_label }}">AssetTag {{forloop.counter}}:</label>
{% if item_form.non_field_errors %}
{{ item_form.non_field_errors }}
{% endif %}
{% if item_form.asset.errors %}
{{item_form.asset.errors}}
{% endif %}
</td>
<td>{{item_form.asset}}</td>
<td>{{item_form.delete}}
{% if item_form.delete.errors %}
{{item_form.delete.errors}}
{% endif %}
</td>
</tr>
{% endfor %}
</table>
<br>
<input class = "btn btn-success" type="submit" value="Delete Selected" />
Cancel
</form>
<form class="AutoForm" action ="." method="POST"> {% csrf_token %}
{{form.as_table}}
<input class = "btn btn-success" type="submit" value="Delete Selected" />
Cancel
</form>
{% endblock %}
When I submit AutoForm, everything is great. It takes me to app_name:url_name, but if I sumbit ManualForm I don't get redirected. It will simply clear all data and reload the form page with empty fields.
The HTTP POST response status code for AutoForm is 302, while for ManualForm is 200.
I don't understand how the template could influence the behavior of the url redirection. What am I doing wrong in the manual rendering of the formset?
It seems that adding:
{% for field in item_form.hidden_fields %}
{{field}}
{% endfor %}
under {% for item_form in form %} will solve the issue.
I didn't understand very well from the docs:
Looping over hidden and visible fields
If you’re manually laying out a form in a template, as opposed to
relying on Django’s default form layout, you might want to treat
< input type="hidden"> fields differently from non-hidden fields. For
example, because hidden fields don’t display anything, putting error
messages “next to” the field could cause confusion for your users – so
errors for those fields should be handled differently.
I just thought this is about errors, so I didn't care. But one of the first thing it says about forms is this:
As an example, the login form for the Django admin contains several
< input> elements: one of type="text" for the username, one of
type="password" for the password, and one of type="submit" for the
“Log in” button. It also contains some hidden text fields that the
user doesn’t see, which Django uses to determine what to do next.
It also tells the browser that the form data should be sent to the URL
specified in the < form>’s action attribute - /admin/ - and that it
should be sent using the HTTP mechanism specified by the method
attribute - post.
Maybe it will help someone else.

Mongoengine, Flask and ReferenceField in WTForms

Hy everybody,
I'm realizing a Flask/MongoDB project and since I am new to this world, I've followed the tutorial at this page:
http://docs.mongodb.org/ecosystem/tutorial/write-a-tumblelog-application-with-flask-mongoengine/
After that, I've started to code my own application and this is part of the code:
MODELS:
class Generic(db.Document):
descrizione = db.StringField(max_length=255, required=True)
meta = {
'allow_inheritance': True,
'indexes': [
{'fields': ['descrizione'], 'unique': True}
]
}
class Category(Generic):
def __call__(self, *args):
pass
class User(db.Document):
email = db.EmailField(max_length=255, required=True)
nickname = db.StringField(max_length=255, required=True)
password = db.StringField(max_length=16, required=True)
categoria = db.ReferenceField('Category', required=True)
meta = {
'indexes': [
{'fields': ['nickname', 'email'], 'unique': True}
]
}
As you can see above, I've a "Category" class which inherits the "Generic" class. The "User" class finally has a ReferenceField to the Category. This way when I create a user, the category field on mongo db is stored as an ObjectID, related to the "generic" collection which has all the categories I've created.
The next step is to create the form to insert new documents into the user collection.
In my Views python file I've this cose:
def iscrizione():
form = model_form(User, only=['email', 'nickname', 'password', 'categoria'])(request.form)
if request.method == 'GET':
ctx = {
'form': form
}
return render_template('users/iscrizione.html', **ctx)
The template uses the Jinja macro reported in the tutorial page:
{% macro render(form) -%}
<fieldset>
{% for field in form %}
{% if field.type in ['CSRFTokenField', 'HiddenField'] %}
{{ field() }}
{% else %}
<div class="clearfix {% if field.errors %}error{% endif %}">
{{ field.label }}
<div class="input">
{% if field.name == "body" %}
{{ field(rows=10, cols=40) }}
{% else %}
{{ field() }}
{% endif %}
{% if field.errors or field.help_text %}
<span class="help-inline">
{% if field.errors %}
{{ field.errors|join(' ') }}
{% else %}
{{ field.help_text }}
{% endif %}
</span>
{% endif %}
</div>
</div>
{% endif %}
{% endfor %}
</fieldset>
{% endmacro %}
And finally, this is my problem
(If you have reached this text, you are my hero)
When I visit the webpage with the rendered form, the macro correctly show the text fields, and for the ReferenceField in my model it show a combo box.
The options values in the select combo are perfectly aligned with the object id of the category documents I've created. Choosing one of these and submitting the form, my application correctly creates the new user document.
Unfortunately, the select box labels doesn't show a human readable value, reporting "Category object".
<select id="categoria" name="categoria">
<option value="530536363c74031c24ee7ab6">Category object</option>
<option value="5305362d3c74031c24ee7ab5">Category object</option>
<option value="530535793c74031b73dd07b4">Category object</option>
</select>
How can I manage to show a correct label for the select box?
Finally I've made it!
Suppose that the field "categoria" of the User document is a ReferenceField to the "Category" collection.
Just add the "label_attr" attribute to "form.categoria" using the field name of the Category model that you want as label.
def iscrizione():
form = model_form(User, only=['email', 'nickname', 'password', 'categoria'])(request.form)
form.categoria.label_attr='descrizione' #<< add this line
if request.method == 'GET':
ctx = {
'form': form
}
return render_template('users/iscrizione.html', **ctx)
This can also be made through the field args in the model_form function:
form = model_form(
User,
only=['email', 'nickname', 'password', 'categoria'],
field_args={'categoria': {'label_attr': 'descrizione'}}
)
Maybe its will be useful to someone. You can use standard approach, like define
def __str__(self):
return self.descrizione
for your Category class

WTForms display foreignkey fields' name in dropdown

I am using appengine webapp2 as wsgihandler, jinja2 as the template engine and wtforms as the form module for its support to app engine models.
Following is my simple model:
class TaskCategory(db.Model):
title = db.StringProperty()
description = db.TextProperty()
class TaskList(db.Model):
title = db.StringProperty()
description = db.TextProperty()
category = db.ReferenceProperty(TaskCategory)
start_date = db.DateProperty()
target_finish_date = db.DateProperty()
Inside my handlers i write stuff as follows:
from wtforms.ext.appengine.db import model_form
model_dict = {'category': TaskCategory,
'task': TaskList}
class CreateForm(webapp2.RequestHandler):
def get(self, slug):
form = model_form(model_dict[slug]) # slug can either be category or task.
self.render_template('index.html', {'form': form()})
Following is my template:
<form method="POST" action"">
<table>
{% for field in form %}
<tr>{{ field.label }}</tr>
<tr>{{ field()|safe }}</tr>
<tr>
{% if field.errors %}
<td>
<ul class=errors>
{% for error in field.errors %}
<li>{{ error }}</li>
{% endfor %}
</ul>
</td>
{% endif %}
{% endfor %}
</table>
<input type="submit" class="btn" value="Submit Form"/>
</form>
Everything renders perfectly in the template, except the dropdown for the foreign key the values listed over there are something like:
<tr><label for="category">Category</label></tr>
<tr><select id="category" name="category"><option selected value="__None"></option><option value="ahhkZXZ-bmV3LXByb2plY3QtdGVtcGxhdGVyEgsSDFRhc2tDYXRlZ29yeRgCDA"><models.TaskCategory object at 0xb22d74c></option>
<option value="ahhkZXZ-bmV3LXByb2plY3QtdGVtcGxhdGVyEgsSDFRhc2tDYXRlZ29yeRgDDA"><models.TaskCategory object at 0xb22dbec></option>
<option value="ahhkZXZ-bmV3LXByb2plY3QtdGVtcGxhdGVyFgsSDFRhc2tDYXRlZ29yeSIEdGVzdAw"><models.TaskCategory object at 0xb22d74c></option></select></tr>
As is visible the names are not being displayed for the category, instead the objects are displayed, how can i rectify it, in a generic manner?
Well this has got nothing to do with, WTForm or jinja or webapp2.
Change your database file to return the title in your case, instead of the object using repr as follows:
class TaskCategory(db.Model):
title = db.StringProperty()
description = db.TextProperty()
def __repr__(self):
return unicode(self.title)

Categories

Resources