I've built several small apps over the last weeks and stumbled every time over the same problem I couldn't figure out a solution with the docs or a google search.
TL;DR
I can't keep the results of a queryset initialized inside __init__ or mount() after clicking a button and changing any other value unrelated to the queryset. Is there a way to save/cache the results?
Simple Example
Initial position
components/simple_unicorn.py
from django_unicorn.components import UnicornView
from any.app.models import MyObject
class SimpleUnicornView(UnicornView):
# Initialize my sample objects empty as I don't have any filter data, yet
objects: MyObject = MyObject.objects.none()
# A single filter value - In reality those are multiple buttons
filter_y: str = ""
# any variable unrelated to the queryset
show_button_x: bool = False
def mount(self):
self.load_data()
def load_data(self):
self.objects = MyObject.objects.filter(any_value=self.filter_y)
# the execution of this query takes very long
def updated_filter_y(self, value):
# after changing variables related to the qs it is obvious the reload the data
self.load_data()
# this is where the problem pops up
def toggle_button_x(self):
# I'm only changing a variable not connected to the loaded data
self.show_button_x = not self.show_button_x
# Now I must execute the query again, otherwise self.objects is empty
self.load_data()
simple.html
<div>
<select unicorn:model="filter_y">
<option value="">-</option>
<option value="A">Filter for "A"</option>
<option value="B">Filter for "B"</option>
</select>
<ul>
{% for obj in objects %}
<li>{{obj}}</li>
{% endfor %}
</ul>
{% if show_button_x %}
<button>X</button>
{% endif %}
<button unicorn:click="toggle_button_x">Toggle Button X</button>
</div>
After clicking on the Toggle Button X the <button>X</button> appears correctly but my list of objects disappears. As if it would re-initialized the MyObject.objects.none() from the start of the view. Ist this possible?
Am I doing something wrong or does anyone know a good workaround?
Unfortunately there is no django-unicorn tag on stackoverflow, yet. Also there are not that many ressources about this incredible easy and powerful framework.
Related
I'm using a Key-Value model to make static template's data dynamic. This is my model's code:
class SiteDataKeyValue(models.Model):
key = models.CharField(max_length=200, verbose_name="Text title")
value = models.TextField(verbose_name="Text")
def __str__(self):
return self.key
For each static page, e.g. About us or Contact us, first I've created the texts in the admin panel. Then, I've written the views below:
class ContactUsPageView(ListView):
"""In this view we are fetching site data from database, in a function named get_context_data.
In this function, we filter the model for objects that contains the word 'contact', therefore
fetching data referring to the about page only. After that the context list is sent to the
template, and there with a for loop and an if statement, each key, value set is chosen for the proper place.
"""
model = SiteDataKeyValue
template_name: str = "core/contact-us.html"
def get_context_data(self, **kwargs):
context = super(ContactUsPageView, self).get_context_data(**kwargs)
context["contact_page_data"] = self.model.objects.filter(key__contains="contact")
return context
Finally, in the template, I'm trying to use these data with a for loop and an if statement as shown below:
{% for text in contact_page_data %}
{% if text.key == "contact us factory address" %}
<p class="card-text"><i class="bi bi-geo-alt me-2">
{{ text.value }}
</p>
{% endif %}
{% endfor %}
I have just one object with this exact key "contact us factory address" in my database. But when the template is rendered, the icon <i class="bi bi-geo-alt me-2"> which is INSIDE of the if statement, gets printed 11 times (NOTE: Just the icon gets printed 11 times, and the value actually gets printed just once)! (11, is the total number of objects the ContactUsPageView view has sent to this template containing the word "contact".) Why is this happening? Why my icon that is INSIDE the if statemnet is shown 11 times instead of just once?
UPDATE: This problem was solved by adding </i> closing tag and there was no errors in other codes.
I have a simple follow/following setup running.
When a user (request.user) see's an object she likes, she can click the follow button and follow that user.
When she returns I want that button on the object to now not be enabled cause she is already following that user.
What is happening is that in the background, the follower/followee record is being made. The object in question has the id of the followee. I just can't figure out how to add that representation to the object_list.
In REST I would add a field to the serializer and that would take care of it. I could then evaluate the truthiness of the new field.
Any ideas on how to accomplish this?
You should do it as a separate query and make the test in the template.
view:
def objects_list(request):
...
return render(request, "the_template_path.html", {
'objects': ObjectName.objects.all()[0:100],
'followed_object_ids': ObjectName.objects.filter(follower=request.user).values_list('id', flat=True)
})
template:
{% for object in objects %}
{% if object.id in followed_object_ids %}
...
{% else %}
...
{% endif %}
{% endfor %}
That will get you started. Obviously you don't want to use generic names like object.
It would then probably be better to move the followed_object_ids query in a middleware so you don't always do it (really useful if you have featured objects widgets everywhere for instance).
In Django you have a multiple form feature called Formsets, which you can use to create multiple forms into the same template. I am trying to achieve something similar in Flask / WTforms.
<form action="{{ url_for('request-accept') }}" method='post'>
<table>
<tbody>
{% for request in requests %}
<tr>
<td>
<div class="person-header">
<img src="{{request.profile_pic_url}}" class="img-circle profile-image"/>
<p class="person-header-text">{{request.fullname()}}</p>
</div>
</td>
<td>
<input type="checkbox" id="{{request.key.urlsafe()}}" name="checkbox{{loop.index}}">
</td>
</tr>
{% endfor %}
</tbody>
</table>
<input class='submit btn btn-primary' type=submit value="Connect">
</form>
The idea is having one form that wrapps all the checkboxes, which the user like to tick to become friends with. As it currently stands I am not really generating any form class in Flask, since I don't know how to make a dynamic FormSet, hence I create the form dynamically inside the html.
The caveat is though, I don't know how to retrieve the selected user id via the checkbox. (I have stored it in the id because I didn't know better)
But I can't access the id in request.values['checkbox1']. I can only see if its on or off.
Any suggestions how to solve this please?
The problem
Your problem is that id is not sent back to the server - only value is ... and since your checkboxes don't have a value attribute the default value is used, which happens to be on.
Since you tagged this with wtforms, I'll give you an example of how you could be doing this.
Never have this issue again
The WTForms' documentation has an example class that will create a list of checkboxes for you:
class MultiCheckboxField(SelectMultipleField):
"""
A multiple-select, except displays a list of checkboxes.
Iterating the field will produce subfields, allowing custom rendering of
the enclosed checkbox fields.
"""
widget = widgets.ListWidget(prefix_label=False)
option_widget = widgets.CheckboxInput()
You would use this field in your custom form in this manner:
class FriendsForm(Form):
potential_friends = MultiCheckboxField("Friends", coerce=int)
# ... later ...
#app.route("/add-friends", methods=["GET", "POST"])
def add_friends():
form = FriendsForm(request.form)
# lookup friends, for now we'll use a static list
form.potential_friends.choices = [(1, "Sam"), (2, "Joe")]
# We'll also need a mapping of IDs to Person instances
# (Made up for this example - use your own ;-) )
mapping = {
1: Person("Sam", profile_pic="sam.jpg"),
2: Person("Joe", profile_pic="joe.png")
}
if request.method == "POST":
# Mark new friends
return render_template("friends.html", form=form, persons=mapping)
Then, in friends.html you can iterate over the form.potential_friends field:
{% for person in form.potential_friends %}
persons[person.data].profile_pic :: {{person.label}} :: {{person}}<br>
{% endfor %}
You can customize your HTML inside the for loop. My particular example should render (with a few more attributes, like for and name):
sam.jpg :: <label>Sam</label> :: <input type="checkbox" value="1">
joe.png :: <label>Joe</label> :: <input type="checkbox" value="2">
I personnally would add a hidden input field in your fieldset under each checkbox with a name such as "friend_nametag1" and a value corresponding to the friend's ID. With the 1 being incremented for every "friend". You can thus look it up in flask view using something like
friend_list = []
list_of_checkboxes = ... (fetch from request.form... ?)
dict_of_friend_nametags = ... (build from request.form... ?)
if 'checkbox1' in list_of_checkboxes:
friend_list.append(dict_of_friend_nametags.get('friend_nametag1')
Obviously you can use some sort of logic to have an incremental index (the "1" in "checkbox1" in this case).
I'm not too familiar with WTForms so there might be a better way to do this, but this solution is fairly straightforward to implement with your current code.
If you want a FieldSet or FormSet I would suggest you use the FormField in conjunction with the FieldList with the related docs here : http://wtforms.simplecodes.com/docs/dev/fields.html#field-enclosures
p.s.: I would not recommend using request as a variable name in your template or your code as it may shadow the global request of flask ? XD
I would like to have a custom snippet of html form code that takes allows the user to select a 'training' that is then used as a query parameter to a django-admin model filter for 'participants'.
I've successfully created the filter on the modeladmin:
class ParticipantAdmin(RestrictedModelAdmin):
list_filter = ('training__name',)
It's probably worth noting that RestrictedModleAdmin is a subclass of ModelAdmin that provides row-level security for the model; logged in users should only see rows they own.
Thus, urls using this filter look something like this when just using that admin interface:
/admin/core/participant/?training__name=Menno+Ropes
All that works great. Now I think I should be able to create a very simple form that allows selecting a valid 'training' and submitting that to /admin/core/participant/ as a GET.
<form method="GET" action="/admin/core/participant/">{% csrf_token %}
<ol>
<li>Select your training:
<select name='training__name'>
<option value=''>—</option>
{% for training in trainings %}
<option value='{{ training.name }}'>{{ training }}</option>
{% endfor %}
</select>
</li>
<li>See participants for that training.
<input type='submit' name='submit' value='Submit' /></li>
</ol>
</form>
This last bit doesn't see to work. Some magic foo in the django innards seems to always mangle the submission to be:
/admin/core/participant/?e=1
This obviously doesn't select the appropriate filter value and thus shows an unfiltered list of 'participants'.
What's going on? What can I do to get it to allow my GET parameter to pass through to the admin model?
Thanks in advance.
PS) Django 1.3+
The problem is that you have a name attribute in your <input type="submit">, causing an extra GET parameter: submit which is throwing the invalid lookup error and thus e=1
Remove the name attribute and you're good to go.
I did a little experiment to confirm since I thought it odd that the server might somehow treat a browser GET differently.
It's a little bit tricky, but it works for me:
def changelist_view(self, request, bill_id, extra_context=None):
"""queryset is an extra parameter"""
req = request.GET.copy()
if 'queryset' in req:
queryset = req.pop('queryset')[0]
else:
queryset = request.META['HTTP_REFERER'].split('queryset=')[1]
url = "/admin/billing/invoice/%s/select_to_move/?%s&queryset=%s" % (bill_id, request.GET.urlencode(), queryset)
return HttpResponseRedirect(url)
request.GET = req
# Do stuff with queryset.
return super(MyAdminClass, self).changelist_view(request, context)
I want to build a Country/State selector. First you choose a country, and the States for that country are displayed in the 2nd select box. Doing that in PHP and jQuery is fairly easy, but I find Django forms to be a bit restrictive in that sense.
I could set the State field to be empty on page load, and then populate it with some jQuery, but then if there are form errors it won't be able to "remember" what State you had selected. I'm also pretty sure that it will throw a validation error because your choice wasn't one of the ones listed in the form on the Python side of things.
So how do I get around these problems?
Here is my solution. It uses the undocumented Form method _raw_value() to peek into the data of the request. This works for forms, which have a prefix, too.
class CascadeForm(forms.Form):
parent=forms.ModelChoiceField(Parent.objects.all())
child=forms.ModelChoiceField(Child.objects.none())
def __init__(self, *args, **kwargs):
forms.Form.__init__(self, *args, **kwargs)
parents=Parent.objects.all()
if len(parents)==1:
self.fields['parent'].initial=parents[0].pk
parent_id=self.fields['parent'].initial or self.initial.get('parent') \
or self._raw_value('parent')
if parent_id:
# parent is known. Now I can display the matching children.
children=Child.objects.filter(parent__id=parent_id)
self.fields['children'].queryset=children
if len(children)==1:
self.fields['children'].initial=children[0].pk
jquery Code:
function json_to_select(url, select_selector) {
/*
Fill a select input field with data from a getJSON call
Inspired by: http://stackoverflow.com/questions/1388302/create-option-on-the-fly-with-jquery
*/
$.getJSON(url, function(data) {
var opt=$(select_selector);
var old_val=opt.val();
opt.html('');
$.each(data, function () {
opt.append($('<option/>').val(this.id).text(this.value));
});
opt.val(old_val);
opt.change();
})
}
$(function(){
$('#id_parent').change(function(){
json_to_select('PATH_TO/parent-to-children/?parent=' + $(this).val(), '#id_child');
})
});
Callback Code, which returns JSON:
def parent_to_children(request):
parent=request.GET.get('parent')
ret=[]
if parent:
for children in Child.objects.filter(parent__id=parent):
ret.append(dict(id=child.id, value=unicode(child)))
if len(ret)!=1:
ret.insert(0, dict(id='', value='---'))
return django.http.HttpResponse(simplejson.dumps(ret),
content_type='application/json')
You could set a hidden field to have the real "state" value, then use jQuery to create the <select> list and, on .select(), copy its value to the hidden field. Then, on page load, your jQuery code can fetch the hidden field's value and use it to select the right item in the <select> element after it's populated.
The key concept here is that the State popup menu is a fiction created entirely in jQuery and not part of the Django form. This gives you full control over it, while letting all the other fields work normally.
EDIT: There's another way to do it, but it doesn't use Django's form classes.
In the view:
context = {'state': None, 'countries': Country.objects.all().order_by('name')}
if 'country' in request.POST:
context['country'] = request.POST['country']
context['states'] = State.objects.filter(
country=context['country']).order_by('name')
if 'state' in request.POST:
context['state'] = request.POST['state']
else:
context['states'] = []
context['country'] = None
# ...Set the rest of the Context here...
return render_to_response("addressform.html", context)
Then in the template:
<select name="country" id="select_country">
{% for c in countries %}
<option value="{{ c.val }}"{% ifequal c.val country %} selected="selected"{% endifequal %}>{{ c.name }}</option>
{% endfor %}
</select>
<select name="state" id="select_state">
{% for s in states %}
<option value="{{ s.val }}"{% ifequal s.val state %} selected="selected"{% endifequal %}>{{ s.name }}</option>
{% endfor %}
</select>
You'll also need the usual JavaScript for reloading the states selector when the country is changed.
I haven't tested this, so there are probably a couple holes in it, but it should get the idea across.
So your choices are:
Use a hidden field in the Django form for the real value and have the select menus created client-side via AJAX, or
Ditch Django's Form stuff and initialize the menus yourself.
Create a custom Django form widget, which I haven't done and thus will not comment on. I have no idea if this is doable, but it looks like you'll need a couple Selects in a MultiWidget, the latter being undocumented in the regular docs, so you'll have to read the source.
Based on Mike's suggestion:
// the jQuery
$(function () {
var $country = $('.country');
var $provInput = $('.province');
var $provSelect = $('<select/>').insertBefore($provInput).change(function() {
$provInput.val($provSelect.val());
});
$country.change(function() {
$provSelect.empty().addClass('loading');
$.getJSON('/get-provinces.json', {'country':$(this).val()}, function(provinces) {
$provSelect.removeClass('loading');
for(i in provinces) {
$provSelect.append('<option value="'+provinces[i][0]+'">'+provinces[i][1]+'</option>');
}
$provSelect.val($provInput.val()).trigger('change');
});
}).trigger('change');
});
# the form
country = CharField(initial='CA', widget=Select(choices=COUNTRIES, attrs={'class':'country'}))
province = CharField(initial='BC', widget=HiddenInput(attrs={'class':'province'}))
# the view
def get_provinces(request):
from django.utils import simplejson
data = {
'CA': CA_PROVINCES,
'US': US_STATES
}.get(request.GET.get('country', None), None)
return HttpResponse(simplejson.dumps(data), mimetype='application/json')