We are using Django_Filterset in our project. I have been asked to set a default filter value for a foreign key column in the model
class RegFilter(django_filters.FilterSet):
class Meta:
model = models.Reg
fields = {
'id': ['exact'],
'nom_id': ['icontains'],
'nom_name': ['icontains'],
'product__name': ['icontains']
}
The product name should default to a product already in the db when the initial screen is displayed - any idea how this can be achieved? Appreciate your help.
I built on the answer given by #Gayathri and fixed a few small issues. He had a typo in his code, and the code wouldn't work with multiple choice fields, since they require the MultiValueDict functionality for multiple entries.
Given a rather regular view, here is the full code (in Python3 syntax):
class BookListView(FilterView):
model = Book
filterset_class = BookFilter
def get_filterset_kwargs(self, filterset_class):
kwargs = super().get_filterset_kwargs(filterset_class)
if kwargs['data'] is None:
filter_values = MultiValueDict()
else:
filter_values = kwargs['data'].copy()
if not filter_values:
# we need to use `setlist` for multi-valued fields to emulate this coming from a query dict
filter_values.setlist('status', ['new', 'ready'])
filter_values['sorting'] = '-created'
kwargs['data'] = filter_values
return kwargs
I managed to solve this one and thought the solution might help someone else in a situation similar to me.
In the views.py, override the FilterView method
def get_filterset_kwargs(self, filterset_class):
kwargs = super(RegFilter, self).get_filterset_kwargs(filterset_class)
if kwargs['data'] is None:
request_dict = {}
else:
request_dict = kwargs['data'].dict()
# This default will not be populated if any other filter options are chosen to restrict the query set
if not request_dict:
request_dict.update({
'product__name': 'ABC Product'
})
request_dict = kwargs['data']
return kwargs
This should now set the default product as 'ABC product' when no other options are passed to restrict the data fetched.
Related
Well, I recently approached to flask-admin and I cannot figure out how to solve this issue. I know that I can use form_choices to restrict the possible values for a text-field by specifying a list of tuples. Anyway, form_choices allows to select only one value at a time. How can I specify that in some cases I may need of a comma-separated list of values?
I tried this workaround:
form_args = {
'FIELD': {
'render_kw': {"multiple": "multiple"},
}
}
but, even though a multiselect input actually appears on the webpage, only the first value is saved.
EIDT 05/13/2017
By playing a bit with flask-admin I found two possible (partial-)solution for my question, both of them with specific drawbacks.
1) The first deals with the use of Select2TagsField
from flask_admin.form.fields import Select2TagsField
...
form_extra_fields = {
'muri': Select2TagsField()
}
With this method is it possible to easily implement select menu for normal input text, even though at present I do not understand how to pass choices to Select2TagsField. It works well as a sort of multiple free text input. However, as far as I understand, it is not possible to pair Select2TagsField and form_choices
2) The second is a bit longer but it offers some more control on code (at least I presume).
Still it implies the use of form_choices, but this time paired with on_model_change
form_args = {
'FIELD': {
'render_kw': {"multiple": "multiple"},
}
}
form_choices = {'FIELD': [
('1', 'M1'), ('2', 'M2'), ('3', 'M3'), ('4', 'M4')
]}
...
def on_model_change(self, form, model, is_created):
if len(form.FIELD.raw_data) > 1:
model.FIELD = ','.join(form.FIELD.raw_data)
This solution, despite the former one, allows to map choices and works well when adding data to the model, but in editing it gives some problems. Any time I open the edit dialog the FIELD is empty. If I look at the data sent to the form (with on_form_prefill by printing form.FIELD.data) I get a comma separated string in the terminal but nothing appear in the pertinent select field on the webpage.
Maybe this is already outdated but I managed to change it and make it work with a multiple choice array field from postgres.
To make it work I extended Select2Field to know how to deal with the list:
class MultipleSelect2Field(Select2Field):
"""Extends select2 field to make it work with postgresql arrays and using choices.
It is far from perfect and it should be tweaked it a bit more.
"""
def iter_choices(self):
"""Iterate over choices especially to check if one of the values is selected."""
if self.allow_blank:
yield (u'__None', self.blank_text, self.data is None)
for value, label in self.choices:
yield (value, label, self.coerce(value) in self.data)
def process_data(self, value):
"""This is called when you create the form with existing data."""
if value is None:
self.data = []
else:
try:
self.data = [self.coerce(value) for value in value]
except (ValueError, TypeError):
self.data = []
def process_formdata(self, valuelist):
"""Process posted data."""
if not valuelist:
return
if valuelist[0] == '__None':
self.data = []
else:
try:
self.data = [self.coerce(value) for value in valuelist]
except ValueError:
raise ValueError(self.gettext(u'Invalid Choice: could not coerce'))
def pre_validate(self, form):
"""Validate sent keys to make sure user don't post data that is not a valid choice."""
sent_data = set(self.data)
valid_data = {k for k, _ in self.choices}
invalid_keys = sent_data - valid_data
if invalid_keys:
raise ValueError('These values are invalid {}'.format(','.join(invalid_keys)))
and to use it do this on the ModelView
class SomeView(ModelView):
form_args = dict(FIELD=dict(render_kw=dict(multiple="multiple"), choices=CHOICES_TUPLE))
form_overrides = dict(FIELD=MultipleSelect2Field)
For this approach to work you would need to use a column that can store a list of elements. At least with sqlite this is not possible using Flask-Admin. However it would be better for you to store your choices in a separate data model and use constraints to link the two models. See a working example here.
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_admin import Admin
from flask_admin.contrib.sqla import ModelView
app = Flask(__name__)
app.config['SECRET_KEY'] = '8e12c91677b3b3df266a770b22c82f2f'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///:memory:'
db = SQLAlchemy(app)
admin = Admin(app)
item_tag_relation = db.Table('item_tag_relation',
db.Column('item_id', db.Integer, db.ForeignKey('item.id')),
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id'))
)
class Item(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String())
tags = db.relationship("Tag",
secondary=item_tag_relation,
backref="items")
def __repr__(self):
return self.name
class Tag(db.Model):
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String())
def __repr__(self):
return self.name
class ItemModelView(ModelView):
pass
db.create_all()
admin.add_view(ItemModelView(Item, db.session))
admin.add_view(ModelView(Tag, db.session))
if __name__ == '__main__':
app.run(debug=True)
I am trying to filter results in a django view using a function as follows:
views.py
def index(request):
european_team_list = Team.objects.all().filter(type = 'Europe')
context = {'european_team_list': european_team_list}
return render(request, 'myapp/index.html', context)
admin.py
class Team(models.Model):
continent = models.CharField()
def _team_type(self):
if self.country = "Europe":
return "Europe"
else:
return "Not Europe"
team_type = property(_team_type)
...other fields...
However, when I load the page, I get an error "Cannot resolve keyword 'team_type' into field. Choices are:" and then it lists all the fields in the Team class other than team_type. Any guidance would be much appreciated.
The simple answer is that you can't do this with the filter() method. filter() is used to construct SQL queries and can only operate on objects at the database level.
So you should figure out how to phrase your query using the database values. It's not clear what your actual code is, but it might look something like:
european_team_list = Team.objects.filter(continent='Europe')
or:
european_team_list = Team.objects.filter(country__in=('France', 'Poland'))
I have been searching around for this for two days now, but I couldn't find any realiable solution.
form:
class SMSSettingsForm(forms.ModelForm):
smsQuota = forms.IntegerField(label=_("Account Quota"), max_value=432000, min_value=1, required=True, help_text=_('(mins)'), error_messages={'required': _('This field cannot be empty')})
smsTimeout = forms.IntegerField(label=_("Timeout"), max_value=9999999, min_value=1,required=False, help_text=_("(mins)"))
class Meta:
model = Settings
fields = ("smsQuota", "smsTimeout")
def __init__(self, *args, **kwargs):
super(SMSSettingsForm, self).__init__(*args, **kwargs)
def save(self):
settings = SettingsManager.get()
settings.smsQuota = self.cleaned_data['smsQuota']
settings.smsTimeout = self.cleaned_data['smsTimeout']
# Following lines are extra fields, rendered by JS in HTML
settings.ck = self.cleaned_data['ck']
settings.ck_per = self.cleand_data['ck_per']
settings.save()
view:
form_with_extra_elem = request.POST.copy()
form_with_extra_elem['ck'] = request.POST.get("ck")
form_with_extra_elem['ck_per'] = request.POST.get("ck_per")
# The two lines above didn't work, so I tried the following, but didn't work again
#form_with_extra_elem.update({'ck': request.POST.get("ck")})
#form_with_extra_elem.update({'ckper': request.POST.get("ck_per")})
form = SMSSettingsForm(form_with_extra_elem)
Do you have any idea how to solve this? What I think is the new element doesn't pass by the validation, so I cannot use them. But how to make them to do so? Actually, I don't need any validation - is there any other way than cleaned_data, to access form parameters?
I don't understand why you want to add extra fields via JS only. If you want them to appear in cleaned_data, they have to be part of the form. You can declare extra fields on a ModelForm simply by specifying them like you have with the other fields:
class SMSSettingsForm(forms.ModelForm):
smsQuota = forms.IntegerField(...)
smsTimeout = forms.IntegerField(...)
ck_per = forms.IntegerField()
ck = forms.IntegerField()
I'm using Django 1.4 with Python 2.7 on Ubuntu 12.10.
I have a form where I need to populate a few drop-downs dynamically (using jQuery) but need 2 of them to be required and the 3rd to be optional.
I'm using Tastypie to help with the API to get the options. Basically the first drop-down is populated with industry level codes for schools. Once a code is selected a category drop-down is populated for all categories for that code. Once the category is chosen a subcategory drop-down is populated for all subcategories for that combination of code and category.
I'm able to require the code drop-down (it's not dynamically populated). However, I'm having a tough time getting the category drop-down to be required. There are basically 2 routes I can take - front-end validation or back-end validation. I'm trying to go with back-end validation so I can easily create further validation if needed.
Here is the form:
class SchoolProductForm(forms.ModelForm):
cip_category = forms.ChoiceField(required=True,
choices=(('', '----------'),))
def __init__(self, *args, **kwargs):
super(SchoolProductForm, self).__init__(*args, **kwargs)
self.fields['short_description'].widget = TA_WIDGET
self.fields['salary_info'].widget = TA_WIDGET
self.fields['job_opportunities'].widget = TA_WIDGET
self.fields['related_careers'].widget = TA_WIDGET
self.fields['meta_keywords'].widget = TI_WIDGET
self.fields['meta_description'].widget = TI_WIDGET
self.fields['cip'].queryset = models.CIP.objects.filter(
parent_id__isnull=True)
class Meta:
model = models.SchoolProduct
exclude = ('campus',)
I've tried to override the clean method. I've tried to create a field specific clean method. Neither seem to work.
Variations of the following:
def clean(self):
super(SchoolProductForm, self).clean()
if cip_category in self._errors:
del self._errors['cip_category']
if self.cleaned_data['cip_category'] == '----------':
self._errors['cip_category'] = 'This field is required.'
return self.cleaned_data
This gives an error that there is no cip_category in cleaned_data, which makes sense because it didn't validate.
I've tried variations with the field specific clean:
def clean_cip_category(self):
data = self.cleaned_data['cip_category']
self.fields['cip_category'].choices = data
return data
But get a validation error on the page stating my choice is not one of the available choices.
I've tried to create a dynamic field type (several variations):
class DynamicChoiceField(forms.ChoiceField):
def valid_value(self, value):
return True
class SchoolProductForm(forms.ModelForm):
cip_category = DynamicChoiceField(required=True,
choices=(('', '----------'),))
But it accepts ---------- as a valid option (which I don't want) and causes an error since the ORM tries to match a value of ---------- in the database (which it won't find).
Any ideas?
I was able to solve this with a little overriding of a method in ChoiceField.
I added the field to the form and handled the pre-population with the self.initial:
class SchoolProductForm(forms.ModelForm):
cip_category = common_forms.DynamicChoiceField(
required=True, choices=(('', '----------'),))
def __init__(self, *args, **kwargs):
super(SchoolProductForm, self).__init__(*args, **kwargs)
self.fields['short_description'].widget = TA_WIDGET
self.fields['salary_info'].widget = TA_WIDGET
self.fields['job_opportunities'].widget = TA_WIDGET
self.fields['related_careers'].widget = TA_WIDGET
self.fields['meta_keywords'].widget = TI_WIDGET
self.fields['meta_description'].widget = TI_WIDGET
self.fields['cip'].queryset = models.CIP.objects.filter(
parent_id__isnull=True)
# Get the top parent and pre-populate
if 'cip' in self.initial:
self.initial['cip'] = models.CIP.objects.get(
pk=self.initial['cip']).top_parent()
class Meta:
model = models.SchoolProduct
exclude = ('campus',)
Where DynamicChoiceField is:
class DynamicChoiceField(forms.ChoiceField):
def valid_value(self, value):
return True
Then, in the view I added handling in the form_valid override:
def form_valid(self, form):
self.object = form.save(commit=False)
# Handle the CIP code
self.object.cip_id = self.request.POST.get('cip_subcategory')
if self.object.cip_id == '':
self.object.cip_id = self.request.POST.get('cip_category')
self.object.save()
I have field in my Model which indicates whether a user wants to receive emails
receive_invites = models.BooleanField(default=True, help_text="Receive an invite email from friends")
I also have a view with an option:
[ x ] I do not want to receive emails...
By default receive_invites is True therefore the checkbox is ticked. However, I'd like the user to tick the checkbox in order to change receive_invites to False. I did the following in my ModelForm to achieve this. Does anybody have a more elegant way of doing this?
class UnsubscribeForm(forms.ModelForm):
class Meta:
model = Entrant
fields = ('receive_invites')
def __init__(self, *args, **kwargs):
if kwargs.has_key('instance'):
instance = kwargs['instance']
if instance.receive_invites:
instance.receive_invites = False
else:
instance.receive_invites = True
super(UnsubscribeForm, self).__init__(*args, **kwargs)
and in the view I have this:
if request.method == 'POST':
unsubscribe_form = UnsubscribeForm(request.POST, instance=me)
if unsubscribe_form.is_valid():
receive_invites = unsubscribe_form.cleaned_data['receive_invites']
if receive_invites:
user.receive_invites = False
else:
user.receive_invites = True
unsubscribe_form.save()
return redirect('index')
else:
unsubscribe_form = UnsubscribeForm(instance=me)
Adding on to #DrTyrsa, it's unreasonable to go through so much convolution just to follow a field naming convention. If you're attached to that field name, you can always add a property to the model that maps the data field to a value you care about:
dont_receive_invites = models.BooleanField(default=False, help_text="Don't receive an invite email from friends")
#property
def receive_invites(self):
return not self.dont_receive_invites
You can't alter the model? If you can, create dont_receive_invites field instead and save a lot of time.
Why not just remove the words "do not" from the view?
[ x ] I want to receive emails...
Otherwise, I'd recommend changing UnsubscribeForm from a ModelForm to a plain Form. Then you can invert booleans all you want without resorting to trickery. It's more work, but it'll work if you can't just change the label.