Pass variable to Python Flask form - python

I'm currently stuck trying to build a variable message flashing function for my Flask forms
Since I'm building a tool that has to have multiple languages available, I wish to create custom messages for my flask form validation based on specific language input.
My form looks something like this:
class messageForm(FlaskForm):
title = StringField(
'title',
validators=[
validators.DataRequired(validationMessage('validateRequired', language=language))
]
)
the function "validationMessage" looks like this:
def validationMessage(message, language):
msg = Message.query.filter_by(title=message).first()
lang = Language.query.filter_by(abbr=language).first()
text = messageBody.query.filter_by(message_id=msg.id, language_id=lang.id).first().text
return text
As you notice, I do some lookup in a few database tables to produce my message text.
My trouble is now...
How do I pass my language variable from my view to my form so that I can pass it on to the "validationMessage" function?
The language variable is based on a variable in my view endpoint
# Messages
#admin.route('/<string:language>/message', methods=['GET'])
def messageView(language='dk')
form=messageForm()
...
I have considered using my session for this, but as I understand it, I can only use this within my view and therefore not within either my form or the message function

You can solve this problem by using the global context variable g. The trick is not to use the builtin validator but a custom validator. The reason is that the builtin validator is a factory function. This means the validationMessage function you pass to it will only be executed on class creation. If you build a custom validator and read the language from the global context variable it will work.
def custom_validator(form, field):
language = g.language
msg = Message.query.filter_by(title=message).first()
lang = Language.query.filter_by(abbr=language).first()
text = messageBody.query.filter_by(message_id=msg.id, language_id=lang.id).first().text
if not len(field.data):
raise validators.ValidationError(text)
Replace the validator in your form with the custom validator:
class messageForm(FlaskForm):
title = StringField('title', validators=[custom_validator])
In the view function just create the language property for the global context variable.
# Messages
#admin.route('/<string:language>/message', methods=['GET'])
def messageView(language='dk')
g.language = language
form=messageForm()
...

Related

flask-smorest response and return type different

I am learning/working on a Rest Api suing flask-smorest and adding the schema using marshmallow.
Below is the code that I am confused with and have a question.
Schemas.py
class ChildAddressDetailsSchema(Schema):
class Meta:
unknown = EXCLUDE
address_id = fields.String(required=True)
address_type = fields.String(required=True)
is_primary = fields.Boolean(required=True)
class ChildAddressDetailsSchemaList(Schema):
class Meta:
unknown = EXCLUDE
person_list = fields.List(fields.Nested(ChildAddressDetailsSchema))
Endpoint Implementation
#address_blueprint.response(status_code=200, schema=ChildAddressDetailsSchema)
#address_blueprint.get('/child/address/<string:person_id>/list')
def get_child_address(person_id):
person_address_list = PersonAddressModel.query.filter_by(person_id=person_id).all()
person_address_dict = [{'address_id': person_address.address_id,
'address_type': person_address.address_type,
'is_primary': person_address.is_primary} for person_address in person_address_list]
return person_address_dict
The part where I have doubt is even though the schema defined in response of blueprint is
ChildAddressDetailsSchema which is not a list , still I get a valid response.Below is the screenshot of the Insomnia from where I am testing the api.
I was expecting an empty response or a error since the return of the get function get_child_address is a list of dictionary which is not as per the schema. Could someone please help me figuring out on to how to fix the issue and return type is strictly informed. Is this something that needs to be coded or does marshmallow handles this.
It's because you called Blueprint.response() before Blueprint.get(). So do like this.
#address_blueprint.get('/child/address/<string:person_id>/list')
#address_blueprint.response(status_code=200, schema=ChildAddressDetailsSchema)
def get_child_address(person_id):
...
A Python decorator returns a new function that calls the original function. So the order of decorators matters in general. In this case, the implementation of the ResponseMixin.response() of Flask Smorest does not work correctly if the Blueprint.route()(which is equivalent to the Scaffold.get()) is not called before.
You can see that on this and this. If the ResponseMixin.response() is called before the Blueprint.route(), the closure wrapper(created at the decorator() inside the ResponseMixin.response()) will be ignored, because the add_url_rule() will be called with the original endpoint function not the wrapper, at the decorator() inside the Blueprint.route().

How to attach a python file to each row(i.e. each data entry) in database formed using Django?

Ive used default django superuser to create an admin and created a DB using it. But now in that i want to add a python script to each data entry i.e. row. How do i do this???
Nothing special. Used basic Django.
There doesn't seem to be anything particularly complex here. You're just asking if you can use a field on the model to choose something; of course you can.
For instance:
# actions.py
def thing1(obj):
# do something with obj
def thing2(obj):
# do something else with obj
# models.py
from . import actions
ACTIONS = {
"do_thing_1": actions.thing1,
"do_thing_2": actions.thing2,
}
ACTION_CHOICES = [(action, action) for action in ACTIONS]
class MyModel(models.Model):
action = models.CharField(max_length=20, choices=ACTION_CHOICES)
def do_action(self):
return ACTIONS[self.action](self)

Django custom decorator user_passes_test() can it obtain url parameters?

Can Django's user_passes_test() access view parameters?
For example I have view that receives an id to retrieve specific record:
def property(request, id):
property = Property.objects.get(id=int(id))
The record has a field named user_id that contains the id for user that originally created record. I want users to be able to view only their own records otherwise be redirected.
I'd like to use a custom decorator which seems simple and clean.
For custom decorator is there some variation of something like this that will work?
#user_passes_test(request.user.id = Property.objects.get(id=int(id)).id, login_url='/index/')
def property(request, id):
property = Property.objects.get(id=int(id))
I have tried creating separate test_func named user_is_property_owner to contain logic to compare current user to property record user_id
#user_passes_test(user_is_property_owner(id), login_url='/index/')
def property(request, id):
property = Property.objects.get(id=int(id))
def user_is_property_owner(property_id):
is_owner = False
try:
Property.objects.filter(id=property_id, user_id=user_id).exists()
is_owner = True
except Property.DoesNotExist:
pass
But having trouble getting current user id and the property id from the request into the user_is_property_owner decorator function.
EDIT to add solution I was using. I did test inside each view test was required. It is simple. i thought using a decorator might be prettier and slightly more simple.
def property(request, id):
# get object
property = Property.objects.get(id=int(id))
# test if request user is not user id on property record
if request.user.id != property.user_id:
# user is not same as property user id so redirect to index
return redirect('index')
# rest of the code if request user is user_id on property record
# eg it is ok to let user into view
Typically, (using class based views), I'll handle this in the get_queryset method so it would be something like
class PropertyDetail(DetailView):
def get_queryset(self):
return self.request.user.property_set.all()
and that will give a 404 if the property isn't for the current user. You might prefer to use a project like django-guardian if you end up with more permission relationships than just Property.
If you take a look at UserPassesTestMixin you'll see that it processes the test_func before calling dispatch so you'll have to call self.get_object(request) yourself if you decide to go that route.

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=[])

How to use Pyramid i18n outside of views and templates?

Pyramid documentation shows us how to use i18n inside views (and templates as well). But how to does one use it outside of views and templates where we have no access to current request (for example, in forms and models)?
#Michael said to pass request to models and forms. But is it right? I mean if form fields defines before __init__() method calls, the same with models. They don't see any parameters from views...
In Pylons we could simply use get_lang() and set_lang() and define preferable language in parent controller and then use ugettext() and ungettext() in any place we want without calling it from request directly every possible time (in views).
How to do that in Pyramid? Note that the language must be set from user's settings (session, cookies, db, etc).
My solution is to create the form class when it's needed with localizer as parameter. For example
forms.py
class FormFactory(object):
def __init__(self, localizer):
self.localizer = localizer
_ = self.localizer
self.required_msg = _(u'This field is required.')
self.invalid_email_msg = _(u'Invalid email address.')
self.password_not_match_msg = _(u'Password must match')
def make_contact_form(self):
_ = self.localizer
class ContactForm(Form):
email = TextField(_(u'Email address'), [
validators.Required(self.required_msg),
validators.Email(self.invalid_email_msg)
])
content = TextAreaField(_(u'Content'), [
validators.Required(self.required_msg)
])
return ContactForm
When you need to use the form
#view_config(route_name='front_pages.contact_us',
renderer='myweb:templates/front_pages/contact_us.genshi')
def contact_us(request):
"""Display contact us form or send mail
"""
_ = get_localizer(request)
factory = FormFactory(_)
ContactForm = factory.make_contact_form()
form = ContactForm(request.params)
return dict(form=form)
As you can see, we get the localizer in the view, and pass it to the FormFactory, then create a form with that factory. By doing that, all messages in the form was replaced with current locale language.
Likewise, you can do the same with model.
Have you found pyramid.18n.get_localizer yet?
http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/i18n.html#using-a-localizer
Actually I had this very same problem. What I ended up doing was to see how the default locale negotiator works - it looks for a LOCALE property on the given request object. So just use a dummy to create the localizer. You may cache this value too, if you want
def my_get_localizer(locale=None):
request = Request({})
request._LOCALE_ = locale
return get_localizer(request)
Alternatively, join the irc channel #pyramid # freenode and pester the guys enough there to split the functionality of get_localizer in 2 separate documented functions (get_localizer and get_localizer_for_locale_name) for us to enjoy ;)
Also, notice that Pyramid TranslationStrings are lazy, so you can translate them as late as you want, e.g.
class MyModel(Base):
description = TranslationString("My model number ${number}")
...
def view(request):
m = MyModel()
localizer = get_localizer(request)
description = localizer.translate(m.description, mapping={'number': 1})
Sidenote: pylons' i18n was the worst can of worms I had opened in ages. The set_lang, get_lang hack was really awful, and pain in the ass as we needed to send emails to users in their native languages and then tried to restore the language back... also, it was IMPOSSIBLE to translate anything outside of a request in a pylons program, as a translator or the registry did not exist then.
You can make a localizer, and then translate a template accordingly.
When making the localizer, you can pass the lang you want (whether you have it from db or else). Hope it can help.
For the sake of clarity, I will set it as 'fr' below
from pyramid.i18n import make_localizer, TranslationStringFactory
from mako.template import Template
from mako.lookup import TemplateLookup
import os
absolute_path = os.path.dirname(os.path.realpath(__file__))
tsf = TranslationStringFactory('your_domain')
mako_lookup = TemplateLookup(directories=['/'])
template = Template(filename=template_path, lookup=mako_lookup)
localizer = make_localizer("fr", [absolute_path + '/../locale/'])
def auto_translate(*args, **kwargs):
return localizer.translate(tsf(*args, **kwargs))
# Pass _ pointer (translate function) to the context
_ = auto_translate
context.update({
"_": _
})
html = template.render(**context)
EDIT
You can also put this logic into a small function
def get_translator(lang):
"""
Useful when need to translate outside of queries (no pointer to request)
:param lang:
:return:
"""
localizer = make_localizer(lang, [absolute_path + '/../locale/'])
def auto_translate(*args, **kwargs):
return localizer.translate(tsf(*args, **kwargs))
_ = auto_translate
return _

Categories