I'm trying to solve this problem. There, I'm trying to use django-tables2 pagination with filters. The problem is the filter does not persist with pagination. However, since the pagination links use GET method, I'm trying to cram the "current" value of my_filter in the querystring.
My harebrained idea: when the index view is called, I can unpack the current value of my_filter and re-apply the filter to my table.
I've created a drop down field containing some choices:
MY_CHOICES = (
('Apple', 'Apple'),
('Ball', 'Ball'),
('Cat', 'Cat'),
)
My model:
class TestModel(models.Model):
my_choices = models.CharField(max_length=20, choices=MY_CHOICES, default="", verbose_name="Choices")
My form:
class TestFilter(forms.ModelForm):
class Meta:
model = TestOrder
fields = ('my_choices',)
My view:
def index(request):
if request.method == "POST":
my_filter = TestFilter(request.POST)
my_selection = my_filter.cleaned_data['my_choices']
my_filter = TestFilter(request.POST)
else:
my_filter = TestFilter()
return render(request, 'my_app/index.html', {'my_filter': my_filter})
Is there is a way to get the "current" value of the my_filter drop down? In other words, is it possible to retrieve data displayed on the web page, after the page has finished loading (i.e. the current selected value of my_filter)?
A related question resolved my query here.
In essence, I should use request.GET for filtering.
Related
I'm using Wagtail, and I want to filter a selection of child pages by a Foreign Key. I've tried the following and I get the error django.core.exceptions.FieldError: Cannot resolve keyword 'use_case' into field when I try children = self.get_children().specific().filter(use_case__slug=slug):
class AiLabResourceMixin(models.Model):
parent_page_types = ['AiLabResourceIndexPage']
use_case = models.ForeignKey(AiLabUseCase, on_delete=models.PROTECT)
content_panels = ArticlePage.content_panels + [
FieldPanel('use_case', widget=forms.Select())
]
class Meta:
abstract = True
class AiLabCaseStudy(AiLabResourceMixin, ArticlePage):
pass
class AiLabBlogPost(AiLabResourceMixin, ArticlePage):
pass
class AiLabExternalLink(AiLabResourceMixin, ArticlePage):
pass
class AiLabResourceIndexPage(RoutablePageMixin, BasePage):
parent_page_types = ['AiLabHomePage']
subpage_types = ['AiLabCaseStudy', 'AiLabBlogPost', 'AiLabExternalLink']
max_count = 1
#route(r'^$')
def all_resources(self, request):
children = self.get_children().specific()
return render(request, 'ai_lab/ai_lab_resource_index_page.html', {
'page': self,
'children': children,
})
#route(r'^([a-z0-9]+(?:-[a-z0-9]+)*)/$')
def filter_by_use_case(self, request, slug):
children = self.get_children().specific().filter(use_case__slug=slug)
return render(request, 'ai_lab/ai_lab_resource_index_page.html', {
'page': self,
'children': children,
})
I've seen this answer, but this assumes I only have one type of page I want to filter. Using something like AiLabCaseStudy.objects.filter(use_case__slug=slug) works, but this only returns AiLabCaseStudys, not AiLabBlogPosts or AiLabExternalLinks.
Any ideas?
At the database level, there is no efficient way to run the filter against all page types at once. Since AiLabResourceMixin is defined as abstract = True, this class has no representation of its own within the database - instead, the use_case field is defined separately for each of AiLabCaseStudy, AiLabBlogPost and AiLabExternalLink. As a result, there's no way for Django or Wagtail to turn .filter(use_case__slug=slug) into a SQL query, since use_case refers to three different places in the database.
A couple of possible ways around this:
If your data model allows, restructure it to use multi-table inheritance - this looks fairly similar to your current definition, except without the abstract = True:
class AiLabResourcePage(ArticlePage):
use_case = models.ForeignKey(AiLabUseCase, on_delete=models.PROTECT)
class AiLabCaseStudy(AiLabResourcePage):
pass
class AiLabBlogPost(AiLabResourcePage):
pass
class AiLabExternalLink(AiLabResourcePage):
pass
AiLabResourcePage will then exist in its own right in the database, and you can query its use_case field with an expression like: AiLabResourcePage.objects.child_of(self).filter(use_case__slug=slug).specific(). There'll be a small performance impact here, since Django has to pull data from one additional table to construct these page objects.
Run a preliminary query on each specific page type to retrieve the matching page IDs, before running the final query with specific():
case_study_ids = list(AiLabCaseStudy.objects.child_of(self).filter(use_case__slug=slug).values_list('id', flat=True))
blog_post_ids = list(AiLabBlogPost.objects.child_of(self).filter(use_case__slug=slug).values_list('id', flat=True))
external_link_ids = list(AiLabExternalLink.objects.child_of(self).filter(use_case__slug=slug).values_list('id', flat=True))
children = Page.objects.filter(id__in=(case_study_ids + blog_post_ids + external_link_ids)).specific()
Try:
children = self.get_children().filter(use_case__slug=slug).specific()
I'd like to retrieve a model's objects via a search form but add another column for search score. I'm unsure how to achieve this using django-tables2 and django-filter.
In the future, I'd like the user to be able to use django-filter to help filter the search result. I can access the form variables from PeopleSearchListView but perhaps it's a better approach to integrate a django form for form handling?
My thought so far is to handle to the get request in get_queryset() and then modify the queryset before it's sent to PeopleTable, but adding another column to the queryset does not seem like a standard approach.
tables.py
class PeopleTable(tables.Table):
score = tables.Column()
class Meta:
model = People
template_name = 'app/bootstrap4.html'
exclude = ('id',)
sequence = ('score', '...')
views.py
class PeopleFilter(django_filters.FilterSet):
class Meta:
model = People
exclude = ('id',)
class PeopleSearchListView(SingleTableMixin, FilterView):
table_class = PeopleTable
model = People
template_name = 'app/people.html'
filterset_class = PeopleFilter
def get_queryset(self):
p = self.request.GET.get('check_this')
qs = People.objects.all()
####
# Run code to score users against "check_this".
# The scoring code I'm using is complex, so below is a simpler
# example.
# Modify queryset using output of scoring code?
####
for person in qs:
if person.first_name == 'Phil' and q == 'Hey!':
score = 1
else:
score = 0
return qs
urls.py
urlpatterns = [
...
path('search/', PeopleSearchListView.as_view(), name='search_test'),
... ]
models.py
class People(models.model):
first_name = models.CharField(max_length=200)
last_name = models.CharField(max_length=200)
Edit:
The scoring algorithm is a bit more complex than the above example. It requires a full pass over all of the rows in the People table to generate a score matrix, before finally comparing each scored row with the search query. It's not a one-off score. For example:
def get_queryset(self):
all = []
for person in qs:
all.append(person.name)
# Do something complex with all,
# e.g., measure cosine distance between every person,
# and finally compare to the get request
scores = measure_cosine(all, self.request.GET.get('check_this'))
# We now have the scores for each person.
So you can add extra columns when you initialise the table.
I've got a couple of tables which do this based on events in the system;
def __init__(self, *args, **kwargs):
"""
Override the init method in order to add dynamic columns as
we need to declare one column per existent event on the system.
"""
extra_columns = []
events = Event.objects.filter(
enabled=True,
).values(
'pk', 'title', 'city'
)
for event in events:
extra_columns.append((
event['city'],
MyColumn(event_pk=event['pk'])
))
if extra_columns:
kwargs.update({
'extra_columns': extra_columns
})
super().__init__(*args, **kwargs)
So you could add your score column similar to this when a score has been provided. Perhaps passing your scores into the table from the view so you can identify they're present and add the column, then use the data when rendering the column.
extra_columns doesn't appear to be in the tables2 docs, but you can find the code here; https://github.com/jieter/django-tables2/blob/master/django_tables2/tables.py#L251
When you define a new column for django-tables2 which is not included in table data or queryset, you should provide a render method to calculate it's value.
You don't have to override get_queryset if a complex filtering, preprocess or join required.
In your table class:
class PeopleTable(tables.Table):
score = tables.Column(accessor="first_name")
class Meta:
model = People
def render_score(self, record):
return 1 if record["first_name"] == "Phil" and q == "Hey!" else 0
In your view you can override and provide complex data as well as special filtering or aggregates with get_context_data:
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["filter"] = self.filter
aggs = {
"score": Function("..."),
"other": Sum("..."),
}
_data = (
People.objects.filter(**params)
.values(*values)
.annotate(**aggs)
.order_by(*values)
.distinct()
)
df = pandas.DataFrame(_data)
df = df....
chart_data = df.to_json()
data = df.to_dict()...
self.table = PeopleTable(data)
context["table"] = self.table
context['chart_data']=chart_data
return context
I am trying to make an publishing option, so i use this
class Article(models.Model):
publish_options = models.CharField(max_length=50)
Now in my form, i used forms.CheckboxSelectMultiple widget. so i have this
PUBLISH_VISIBILITY = (
('All', 'All'),
('Paid-users', 'Paid-users'),
('Free Users', 'Free Users'),
('Public', 'Public'),
)
class PortalNoteForm(ModelForm):
publish_options = forms.MultipleChoiceField(widget=forms.CheckboxSelectMultiple, choices=PUBLISH_VISIBILITY)
class Meta:
model = Article
Now in my view i get the values for the checkbox like this
if request.method == 'POST':
form = PortalNoteForm(request.POST)
if form.is_valid():
school_article = form.save(commit=False)
school_article.publish_options = form.cleaned_data['publish_options']
school_article.school_creator = admin
school_article.save()
return HttpResponseRedirect(reverse('going to somewhere'))
else:
form = PortalNoteForm()
context = {'form':form, 'notes':notes}
Okay this works fine saving the value of the publish_option but as a list, even if you select only one checkbox its value comes as a list. Now the problem here is i cant get to stop these publish_options value from displaying as a list in django template. I have tried iterating over them but no way. I really need help.
Well you already had a form, why do you still getting data from request.POST? Form is suppose to take request.POST and convert data into a more convenient way for you to use:
views.py
def view_func(request):
form = PortalNoteForm(request.POST or None)
if form.is_valid():
options = form.cleaned_data['public_options']
# now you have options so use it
I am trying to iterate over form results and I can't help but think that I am re-inventing the wheel here.
filterlist = []
if request.POST:
form = FilterForm(request.POST)
if form.is_valid():
for key, value in form.cleaned_data.iteritems():
filterlist.append(key)
filterlist.append(value)
This works, but seems very awkward and creates lots of other problems. For example the values come back with u' so I have to use value.encode("utf8") but then if a value is None it throws in error. So now I have to check if it is None, if not then encode. There has to be a better way.
EDIT: What I am trying to do.
I am trying to filter what is shown on a page. The problem I am running into is that if a value is empty (the user don't fill the box because they only want to filter against one object) then I get no results. For example a user wants to search for all books by the author name "Smith" but doesn't want to search against a genre.
results = Books.objects.filter(author=author, genre=genre)
The user would get no results because this is an AND search. But, if a user put in "Smith" for the author and "mystery" for the genre then it works exactly like I want it to, only giving results where both are true.
So, I am trying to eliminate the empty stuff by iterating over the form results. Like I said I am probably re-inventing the wheel here.
In Python 3 use:
for key, value in form.cleaned_data.items():
If the field names are the same in the model and the form, try this:
filter = {}
if request.method == 'POST':
form = FilterForm(request.POST)
if form.is_valid():
for key, value in form.cleaned_data.iteritems():
if value:
filter[key] = value
results = Books.objects.filter(**filter)
Python is one of the few languages having named parameters. You can assemble a dict with the non-empty form fields and pass it to the filter method using the kwargs unpacking operator **.
For example:
kwargs = {"author": "Freud"}
results = Books.objects.filter(**kwargs)
Is the same as:
results = Books.objects.filter(author="Freud")
I think the problem is that by default the Model form is not valid if a form field does not have a value entered by the user, if you don`t require the field every time from the user you need to set the required field to false in the ModelForm class in forms.py as shown in the code below. Remember that the field is set false only in the model form not in the model itself
class myForm(forms.ModelForm):
myfield_id = forms.CharField(required=False)
myfield_foo = forms.CharField(required=False)
myfield_bar = forms.CharField(required=False)
myfield_name = forms.CharField(required=False)
class Meta:
model = myModel
exclude = ('myfield_ex','myfield_file')
fields = ['myfield_id','myfield_foo','myfield_bar','myfield_name',]
After you have the form entered by the user what you need is use the Q object which can be used to create complex queries as described in the manula page here
https://docs.djangoproject.com/en/1.7/topics/db/queries/#complex-lookups-with-q
A simple example code would look like
if form.is_valid():
qgroup = []
for key,value in form.cleaned_data.iteritems():
if value:
q_name = Q(**{"%s"%format(filterKey[key]) : value})
qgroup.append(q_name)
q = None
# can use the reduce as shown here qgroup = reduce(operator.or_, (Q(**{"{0}".format(filterKey[key]): value}) for (key,value) in form.cleaned_data.iteritems()))
for key,value in form.cleaned_data.iteritems():
if value:
q_name = Q(**{"%s"%format(filterKey[key]) : value})
qgroup.append(q_name)
for x in qgroup:
q &= x ### Or use the OR operator or
if q:
resultL = myModel.objects.filter(q).select_related()
The filterKey can look something on the lines of
filterKey = {'myfield_id' : "myfield_id",
'myfield_foo' : "myfield_foo__icontains",
'myfield_bar' : "myfield_bar__relative_field__icontains",
}
I am trying to generate a form in WTForms that has dynamic fields according to this documentation http://wtforms.simplecodes.com/docs/1.0.2/specific_problems.html#dynamic-form-composition
I have this subform class which allows users to pick items to purchase from a list:
class Item(Form):
itmid = SelectField('Item ID')
qty = IntegerField('Quantity')
class F(Form):
pass
There will be more than one category of shopping items, so I would like to generate a dynamic select field based on what categories the user will choose:
fld = FieldList(FormField(Item))
fld.append_entry()
but I get the following error:
AttributeError: 'UnboundField' object has no attribute 'append_entry'
Am I doing something wrong, or is there no way to accomplish this in WTForms?
I ran into this issue tonight and ended up with this. I hope this helps future people.
RecipeForm.py
class RecipeForm(Form):
category = SelectField('Category', choices=[], coerce=int)
...
views.py
#mod.route('/recipes/create', methods=['POST'])
def validateRecipe():
categories = [(c.id, c.name) for c in g.user.categories.order_by(Category.name).all()]
form = RecipeForm(request.form)
form.category.choices = categories
...
#mod.route('/recipes/create', methods=['GET'])
def createRecipe():
categories = [(c.id, c.name) for c in g.user.categories.order_by(Category.name).all()]
form = RecipeForm(request.form)
form.category.choices = categories
return render_template('recipes/createRecipe.html', form=form)
I found this post helpful as well
class BaseForm(Form):
#classmethod
def append_field(cls, name, field):
setattr(cls, name, field)
return cls
from forms import TestForm
form = TestForm.append_field("do_you_want_fries_with_that",BooleanField('fries'))(obj=db_populate_object)
I use the extended class BaseForm for all my forms and have a convenient append_field function on class.
Returns the class with the field appended, since instances (of Form fields) can't append fields.
Posting without writing full code or testing the code, but maybe it will give you some ideas. Also this could maybe only help with the filling the needed data.
You need to fill choices for SelectField to be able to see the data and be able to select it. Where you fill that? Initial fill should be in the form definition, but if you like dynamic one, I would suggest to modify it in the place where you creating this form for showing to the user. Like the view where you do some form = YourForm() and then passing it to the template.
How to fill form's select field with choices? You must have list of tuples and then something like this:
form.category_select.choices = [(key, categories[key]) for key in categories]
form.category_select.choices.insert(0, ("", "Some default value..."))
categories here must be dictionary containing your categories in format like {1:'One', 2:'Two',...}
So if you will assign something to choices when defining the form it will have that data from the beginning, and where you need to have user's categories, just overwrite it in the view.
Hope that will give you some ideas and you can move forward :)
have you tried calling append_entry() on the form instance instead of the FieldList definition?
class F(Form)
fld = FieldList(SelectField(Item))
form = F()
form.fld.append_entry()
This is how i got it to work.
class MyForm(FlaskForm):
mylist = SelectField('Select Field', choices=[])
#app.route("/test", methods=['GET', 'POST']
def testview():
form = MyForm()
form.mylist.choices = [(str(i), i) for i in range(9)]
Strangely this whole thing stops working for me if i use coerce=int. I am myself a flask beginner, so i am not really sure why coerce=int causes issue.
WTForms Documentation : class wtforms.fields.SelectField
Select fields with dynamic choice values:
class UserDetails(Form):
group_id = SelectField(u'Group', coerce=int)
def edit_user(request, id):
user = User.query.get(id)
form = UserDetails(request.POST, obj=user)
form.group_id.choices = [(g.id, g.name) for g in Group.query.order_by('name')]