How to define a selection field in flask - python

I want to define a selection field in python, i.e. field that is limited to a set of values. How can I do that in flask framework. I could not find anything on selection fields in the following sources:
Declaring Models
SQLAlchemy in Flask
I am using sqlalchemy for ORM.

I assume you mean a field in a form that has a limited set of options; to do this you can use WTForms and its extensions which allow you to create forms from models.
Once you have done that, you can then limit the choices for a field based on a model condition.
As you haven't posted your model, here is the example give you give you an idea on how this would work:
def enabled_categories():
return Category.query.filter_by(enabled=True)
class BlogPostEdit(Form):
title = TextField()
blog = QuerySelectField(get_label='title')
category = QuerySelectField(query_factory=enabled_categories,
allow_blank=True)
def edit_blog_post(request, id):
post = Post.query.get(id)
form = ArticleEdit(obj=post)
# Since we didn't provide a query_factory for the 'blog' field, we need
# to set a dynamic one in the view.
form.blog.query = Blog.query.filter(Blog.author == request.user) \
.order_by(Blog.name)

Related

Flask with SQLAlchemy persisting large form (newbie)

I'm writing a lot of boilerplate like code to try and persist my form data. In other languages I've not had to manually map the form attribute to the model prior to saving I'm wondering does python/flask has a similar approach? I'd ideally prefer to manually map all my form fields to simply persist a db record.
[edit]
To be clear this approach works fine for me the records are persisted properly however it seems kind of inefficient. In another piece of code I was able to use the wtf library {{ wtf.quick_form(form) }}
to create a form an html form without explicitly listing all its fields. Given I have the same names for my model attributes as I do for the form attributes I was wondering if I can do the same?
[edit]
Any feedback
Example below..
I have a largish model "product"
class Product(db.Model):
field1 = db.Column(db.String(200))
field2 = db.Column(db.String(200))
....
field30 = db.Column(db.String(200))
It's represented as a flask form e.g.
class ProductForm(FlaskForm):
field1 = StringField('Field 1', validators=[DataRequired()])
field2 = TextAreaField('Field 2 Label')
field30 = TextAreaField('Field 30 Label')
submit = SubmitField('Submit')
Here I try and map and persist the product record...
def save_product(self, product, form):
product.field1 = form.field.data
#lots more tedious form to model mapping
db.session.add(product)
# commit the data to the database
db.session.commit()
In my routes.py I have the following. I see it being called and the record does indeed persist.
def crud():
form = CrudForm()
if form.validate_on_submit():
product = Product()
product.save_product(product, form, new=True)
flash(_('Product Saved'))
return redirect(url_for('main.index'))
return render_template('crud.html', title=_('Product CRUD'),
form=form)
Given that the names of your form fields are the same as the names of your database columns it is possible to cut down on the boilerplate. A dict of all of the fields in your form is accessible as the form.data property and this can be passed directly through as keyword arguments when your create the new instance of your model rather than setting them one at a time afterwards.
product = Product(**form.data)

Python WTForms: How to dynamically set options of a SelectField in FieldList of FormFields?

I am currently trying to use WTForms's FieldList and FormField enclosures to allow users to add a custom subset of locations with corresponding coverage amounts.
The form presents a user with some standard inputs and then initializes with a single set of fields (FormField) each with a location select input and a coverage amount input.
Javascript allows the user to add or remove additional location coverage field sets as needed to reflect the accurate document information.
The Problem: As a workaround, I have been setting the location options by passing a template variable in the form handler's GET request and manually creating my own form fields. This isn't updating the actual WTForms location field choices though, so when I submit the form an exception is raised for the location field ('Not a valid choice').
How can I dynamically add location choices to the location field of the LocationForm when I instantiate MyForm?
This is basically how my code looks:
Note: I have omitted the code that creates a locations template variable in the GET request since that is not the desired design. I want to be more in line with the intended WTForms methodology:
class LocationForm(Form):
location = SelectField('Location', [], choices=[])
coverage = FloatField('Coverage', [])
class MyForm(BaseForm):
# other fields omitted for brevity
location_coverage = FieldList(FormField(LocationForm), [], min_entries=1)
class AddDocument(BaseHandler):
def get(self):
params = {
"cid": cid
}
return self.render_template("form.html", **params)
def post(self):
cid = self.request.get('cid')
if not self.form.validate():
return self.get()
company_key = ndb.Key('Company', cid)
doc = Document(parent=company_key)
self.form.populate_obj(doc)
doc.put()
params = {
"cid":
}
return self.redirect_to('view_company', **params)
#webapp2.cached_property
def form(self):
f = MyForm(self)
# HERE is where I would normally do something like:
# company = ndb.Key('Company', int(self.request.get('cid')))
# locations = ndb.Location.query(ancestor=company).fetch()
# f.field_name.choices = [(loc.key, loc.name) for loc in locations]
# but this doesn't work with Select Fields enclosed in
# FormFields and FieldLists.
return f
Edit:
I created a solution but this isn't the answer I am looking for. In my case, I simply changed the LocationForm.location form field from a SelectField to a StringField. Doing this bypasses the validation of the select field choices and allows the form to submit. This is not ideal as it is not the intended design, but if anyone can steer me toward a more proper way to use WTForms in this particular scenario I would greatly appreciate it.
If your BaseForm class populates the form from the post data on instantiation, you should see the nested forms populated at the point where you'd normally add the choices into a SelectField directly on the form.
Therefore something like:
for entry in f.location_coverage.entries:
entry.location.choices = [(loc.key, loc.name) for loc in locations]
Should populate the choices into each subform select field.

Can model views in Flask-Admin hyperlink to other model views?

Let's suppose we have a model, Foo, that references another model, User - and there are Flask-Admin's ModelView for both.
On the Foo admin view page
I would like the entries in the User column to be linked to the corresponding User model view.
Do I need to modify one of Flask-Admin's templates to achieve this?
(This is possible in the Django admin interface by simply outputting HTML for a given field and setting allow_tags (ref) True to bypass Django's HTML tag filter)
Some example code based on Joes' answer:
class MyFooView(ModelView):
def _user_formatter(view, context, model, name):
return Markup(
u"<a href='%s'>%s</a>" % (
url_for('user.edit_view', id=model.user.id),
model.user
)
) if model.user else u""
column_formatters = {
'user': _user_formatter
}
Use column_formatters for this: https://flask-admin.readthedocs.org/en/latest/api/mod_model/#flask.ext.admin.model.BaseModelView.column_formatters
Idea is pretty simple: for a field that you want to display as hyperlink, either generate a HTML string and wrap it with Jinja2 Markup class (so it won't be escaped in templates) or use macro helper: https://github.com/mrjoes/flask-admin/blob/master/flask_admin/model/template.py
Macro helper allows you to use custom Jinja2 macros in overridden template, which moves presentational logic to templates.
As far as URL is concerned, all you need is to find endpoint name generated (or provided) for the User model and do url_for('userview.edit_view', id=model.id) to generate the link.
extra information for #wodow, notice that model.user is wrong if you use pymongo as the backend, because the model in pymongo is a dict type, you can just use model['name'] to replace it
Adding this code to each of your models that have referenced by other models and flask-admin and jinja will take care of the name you want to display on the screen, just replace that with whatever you prefer:
def __unicode__(self):
return self.name # or self.id or whatever you prefer
for example:
class Role(db.Document, RoleMixin):
name = db.StringField(max_length=80, unique=True)
description = db.StringField(max_length=255)
def __unicode__(self):
return self.name
class MasterUser(db.Document, UserMixin):
email = db.StringField(max_length=255)
password = db.StringField(max_length=255)
active = db.BooleanField(default=True)
confirmed_at = db.DateTimeField()
roles = db.ListField(db.ReferenceField(Role), default=[])

Making disabled field in ModelForm subclass

I got a model Layout in my Django app with the following fields:
meta_layout - ForeignKey on model MetaLayout
name - CharField
edited - DateTimeField
is_active - BooleanField
And I have two views using this model - one called NewLayout and other EditLayout each subclassing standard CreateView and UpdateView accordingly. In EditLayout view I want to use some special form that looks the same as form used in NewLayout (which is simply plain ModelForm for this model) but has meta_layout select field displayed with attribute disabled="disabled" (e.d. user can choose meta_layout for each Layout only once - while creating it). Ok, I can create custom ModelForm where widget for meta_layout field has the desired attribute, but the problem is actually that when such attribute set on form field it will not send any values with request - so my validation fails trying to check value for this field and select element does not support "readonly" attribute which will would be just fine here.
I found some really ugly hack to workaround this:
#Here is my Form:
class LayoutEditForm(forms.ModelForm):
meta_layout = forms.ModelChoiceField(
queryset=MetaLayout.objects.all(),
widget=forms.Select(attrs=dict(disabled='disabled')),
empty_label=None,
required=False) # if required=True validation will fail
# because value is not supplied in POST
class Meta:
fields = ('meta_layout', 'name', 'is_active')
model = Layout
class EditLayout(UpdateView):
...
# And one modified method from my View-class
def get_form_kwargs(self):
kwargs = super(EditLayout, self).get_form_kwargs()
# actually POST parameters
if kwargs.has_key('data'):
# can't change QueryDict itself - it's immutable
data = dict(self.request.POST.items())
# emulate POST params from ModelChoiceField
data['meta_layout'] = u'%d' % self.object.meta_layout.id
kwargs['data'] = data
return kwargs
But I believe that it's non-Django, non-Pythonic and not a good-programming-style-at-all of doing such simple thing. Can you suggest any better solution?
Edit:
Oh, I found much less ugly solution: added this in my form class:
def clean_meta_layout(self):
return self.instance.meta_layout
But I still open for suggestions) - may I missed something?

Django admin, filter objects by ManyToMany reference

There's photologue application, simple photo gallery for django, implementing Photo and Gallery objects.
Gallery object has ManyToMany field, which references Photo objects.
I need to be able to get list of all Photos for a given Gallery. Is it possible to add Gallery filter to Photo's admin page?
If it's possible, how to do it best?
You need to write a custom FilterSpec! Custom Filter in Django Admin on Django 1.3 or below
It'll look like this:
from django.contrib.admin.filterspecs import RelatedFilterSpec, FilterSpec
from models import Gallery
class GalleryFilterSpec(RelatedFilterSpec):
def __init__(self, f, request, params, model, model_admin):
self.lookup_kwarg = f.name
self._lookup_model = f.rel.to
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
self.user = request.user
self.lookup_choices = [(g.pk, g.name) for g in Gallery.objects.all()]
def has_output(self):
return len(self.lookup_choices) > 1
def title(self):
return self._lookup_model._meta.verbose_name
FilterSpec.filter_specs.insert(0,
(lambda f: f.rel.to == Gallery, GalleryFilterSpec))
Put it in a module filters.py in your app package and import it in you admin.py (it's important to import it, so that the filter becomes registered on the admin site!)
EDIT: "f" is the field instance, in this case models.ManyToManyField The last line registers the FilterSpec for all fields that have a relation to the Gallery model. This will not work as you mentioned if the field is defined on the Gallery model, since django.contrib.admin.views.main.ChangeList.get_filters checks if the field you define in the list really exist on the model (doesnt work for related_name either). I think the easiest way around is that you could make a custom template for that changelist and hardcode your filter in there, the FilterSpec itself isn't need for the filtering itself, django uses just the url get parameters for that!
Well, that's how I've done it.
I made custom admin template "change_list.html". Custom template tag creates a list of all existing galleries. Filtering is made like this:
class PhotoAdmin(admin.ModelAdmin):
...
def queryset(self, request):
if request.COOKIES.has_key("gallery"):
gallery = Gallery.objects.filter(title_slug=request.COOKIES["gallery"])
if len(gallery)>0:
return gallery[0].photos.all()
return super(PhotoAdmin, self).queryset(request)
Cookie is set with javascript.
For future reference for others to find, if you have a relationship it's bi-directional, so you can get the photos for galleries or the galleries for a photo via a ModelAdmin.
Let's say you have a changelist view for your Photo model:
from django.contrib import admin
from yourapp.models import Photo
class PhotoAdmin(admin.ModelAdmin):
list_filter = ('galleries', )
admin.site.register(Photo, PhotoAdmin)
Then in the admin you'll see a filter showing all of the galleries and if you click one it'll filter the list to show you only photos for that gallery.
Of course, this may not be practical if there are a LOT of galleries, but you can get there just by using the well-documented ModelAdmin rather than hacking together a template or filterspec.
http://docs.djangoproject.com/en/dev/ref/contrib/admin/#modeladmin-objects
#Jough Dempsey pointed out you maybe don't need a custom FilterSpec just for m2m fields.
However today I found I wanted one for a django-taggit tag field. The tags are basically an m2m relation but it complains that 'TaggableManager' object has no attribute 'get_choices' if you try and add the tag field into list_filter.
In this case it was #lazerscience's code to the rescue...
However it didn't work when used against Django 1.3, needed a couple of new lines added, compare my version below which works:
class TagFilterSpec(RelatedFilterSpec):
def __init__(self, f, request, params, model, model_admin, field_path=None):
super(RelatedFilterSpec, self).__init__(
f, request, params, model, model_admin, field_path=field_path)
self.lookup_title = f.verbose_name # use field name
self.lookup_kwarg = f.name
self.lookup_kwarg_isnull = '%s__isnull' % (self.field_path)
self._lookup_model = f.rel.to
self.lookup_val = request.GET.get(self.lookup_kwarg, None)
self.lookup_val_isnull = request.GET.get(
self.lookup_kwarg_isnull, None)
self.user = request.user
self.lookup_choices = [(g.pk, g.name) for g in Tag.objects.all()]
def has_output(self):
return len(self.lookup_choices) > 1
def title(self):
return self._lookup_model._meta.verbose_name
FilterSpec.filter_specs.insert(0,
(lambda f: f.rel.to == Tag, TagFilterSpec))

Categories