Q: Django crispy forms - change behavior depending url - html

I don't know how to implement this with django crispy forms.
I have an interface with a URL like this:
myurl.com/movements/new
And I have a select in the form with the type of movement.
When there is not a type of movement explicitly assigned, just shows the select without any option selected.
When user access to form with an URL like myurl.com/movements/income/
I want this select to have by default the income option.
And so on with every possible option.
I know that I can use JavaScript for this, but I think that it would be better to have it on the back-end.
How can I achieve this on the back-end part?

models.py:
class MyModel(models.Model):
CHOICES = (
('Income', 'Income'),
('Option2', 'Option2'),
('Option3', 'Option3'),
)
choice = models.CharField(max_length=25, choices=CHOICES)
urls.py:
urlpatterns = [
url(
regex=r'^new/(?P<option>[\w.#+-]+)/$', # feel free to adjust the regex
view=views.NewCreateView.as_view(),
name='new'
),
url(
regex=r'^new/$',
view=views.NewCreateView.as_view(),
name='new'
)
]
views.py:
class NewCreateView(CreateView):
model = MyModel
fields = ['choice']
def get_form_kwargs(self):
form_kwargs = super().get_form_kwargs()
if 'option' in self.kwargs:
if any(self.kwargs['option'] in choice for choice in MyModel.CHOICES):
form_kwargs['initial']['choice'] = self.kwargs['option']
return form_kwargs
The initial selection of the drop-down list is only given if you visit the URL new/ with a valid option like new/Income. Of course, you can adjust the URL according to your needs.
You could also override get_initial instead of get_form_kwargs.

Related

Generation admin panel with custom column type in Websauna

I have a SQLAlchemy model with custom type ChoiceType which comes from sqlalchemy_utils library.
class Recipient(Base, BaseMixin):
first_name = Column(String())
last_name = Column(String())
social_network = Column(ChoiceType(SOCIAL_NETWOKRS))
Where SOCIAL_NETWOKRS are SOCIAL_NETWOKRS = [
('vk', 'Vkontakte'),
('fb', 'Facebook'),
('youtube', 'Youtube'),
]
I got next error when going into admin panel for edit my model:
NotImplementedError: Not able to derive a colander type from sqlalchemy type: ChoiceType(length=255) Please explicitly provide a colander `typ` for the "social_network" Column.
How can I get around the restriction with saving autogeneration of the administrative panel?
I move from sqlalchemy_utils and add direct validation from a Colander.
Next snippet works as expected:
class Account(BaseMixin, Base):
social_network = Column(String(), info={'colanderalchemy': {
'typ': colander.String(),
'widget': deform.widget.SelectWidget(values=SOCIAL_NETWOKRS),
}})

Django admin: allowing some HTML in user input

By default, the Django admin strips away all HTML tags from user input. I'd like to allow a small subset of tags, say <a>. What's the easiest way to do this? I know about allow_tags, but it's deprecated. I also want to be careful about manually marking strings as safe that aren't.
If external library isn't a burden for you, then you must try django-bleach, it will suffice your requirement. It returns valid HTML that only contains your specified allowed tags.
Configuration:
in settings.py
BLEACH_ALLOWED_TAGS = ['p', 'b', 'i', 'u', 'em', 'strong', 'a']
BLEACH_ALLOWED_ATTRIBUTES = ['href', 'title', 'style']
BLEACH_STRIP_TAGS = True
Use cases:
1. In your models:
from django import models
from django_bleach.models import BleachField
class Post(models.Model):
title = models.CharField()
content = BleachField()
2. In your forms:
class PostForm(forms.ModelForm):
content = BleachField()
class Meta:
model = Post
fields = ['title', 'content']
In your templates:
{% load bleach_tags %}
{{ unsafe_html|bleach }}
for more usage, I suggest you must read the documentation. Its quite easy and straight forward.
documentation
You can use format_html() or mark_safe() in place of allow_tags. Although, like you were saying, mark_safe() probably isn't a good idea for user input.
format_html(): https://docs.djangoproject.com/en/1.9/ref/utils/#django.utils.html.format_html
mark_safe(): https://docs.djangoproject.com/en/1.9/ref/utils/#django.utils.safestring.mark_safe

Can you include a TaggableManager as a filterset for django-filter?

I'm using both django-taggit and django-filter in my web application, which stores legal decisions. My main view (below) inherits from the stock django-filter FilterView and allows people to filter the decisions by both statutes and parts of statutes.
class DecisionListView(FilterView):
context_object_name = "decision_list"
filterset_class = DecisionFilter
queryset = Decision.objects.select_related().all()
def get_context_data(self, **kwargs):
# Call the base implementation to get a context
context = super(DecisionListView, self).get_context_data(**kwargs)
# Add in querysets for all the statutes
context['statutes'] = Statute.objects.select_related().all()
context['tags'] = Decision.tags.most_common().distinct()
return context
I also tag decisions by topic when they're added and I'd like people to be able to filter on that too. I currently have the following in models.py:
class Decision(models.Model):
citation = models.CharField(max_length = 100)
decision_making_body = models.ForeignKey(DecisionMakingBody)
statute = models.ForeignKey(Statute)
paragraph = models.ForeignKey(Paragraph)
...
tags = TaggableManager()
class DecisionFilter(django_filters.FilterSet):
class Meta:
model = Decision
fields = ['statute', 'paragraph']
I tried adding 'tags' to the fields list in DecisionFilter but that had no effect, presumably because a TaggableManager is a Manager rather than a field in the database. I haven't found anything in the docs for either app that covers this. Is it possible to filter on taggit tags?
You should be able to use 'tags__name' as the search/filter field. Check out the Filtering section on http://django-taggit.readthedocs.org/en/latest/api.html#filtering

Frequent update one filed of django model

Imagine, I have News models with many text fields
class News(models.Model):
title = models.CharField(max_length=255)
subtitle = models.CharField(max_length=255, blank=True)
lead = models.TextField(max_length=4096)
content = models.TextField()
...
last_visited = models.DateTimeField()
Every time my News object outputs, I update last_visited field:
news.last_visited = datetime.datetime.now()
news.save()
This code makes Django override all model fields:
UPDATE news SET title='...', subtitle='...', last_visited = '...' WHERE id = '...';
Instead of just one:
UPDATE news SET last_visited = '...' WHERE id = '...';
I worried how bad it is and is it worth of thinking about.
Django documentation offers queryset update but it looks not very elegant:
def upd(obj, **kwargs):
obj.__class__._default_manager.filter(pk=obj.pk).update(**kwargs)
upd(news, last_visited=datetime.datetime.now())
I use mysql backend.
Using update but with a cleaner approach:
class News(models.Model):
def update_visited(self):
News.objects.filter(pk=self.pk).update(
last_visited=datetime.datetime.now())
I think using queryset update is good. It removes the possibility that you overwrite changes to other fields by accident.
I know you're worried that it looks inelegant, but you only have to use it once in your upd function, then use upd in your views.
Supposing you want to use this on more than one model (guessing this because you pass obj to your upd function) it would probably make sense to have some base class that implements the last_visited field and your News class inherits from this class... Then you could do the update just on your base class.... Another possibilty would be putting the last_visited information into a seperate model and referencing the News model either through a ForeignKey or a GenericForeignKey (in the case you want to keep a 'history' for different models).

Unique validator in WTForms with SQLAlchemy models

I defined some WTForms forms in an application that uses SQLALchemy to manage database operations.
For example, a form for managing Categories:
class CategoryForm(Form):
name = TextField(u'name', [validators.Required()])
And here's the corresponding SQLAlchemy model:
class Category(Base):
__tablename__= 'category'
id = Column(Integer, primary_key=True)
name = Column(Unicode(255))
def __repr__(self):
return '<Category %i>'% self.id
def __unicode__(self):
return self.name
I would like to add a unique constraint on the form validation (not on the model itself).
Reading the WTForms documentation, I found a way to do it with a simple class:
class Unique(object):
""" validator that checks field uniqueness """
def __init__(self, model, field, message=None):
self.model = model
self.field = field
if not message:
message = u'this element already exists'
self.message = message
def __call__(self, form, field):
check = self.model.query.filter(self.field == field.data).first()
if check:
raise ValidationError(self.message)
Now I can add that validator to the CategoryForm like this:
name = TextField(u'name', [validators.Required(), Unique(Category, Category.name)])
This check works great when the user tries to add a category that already exists \o/
BUT it won't work when the user tries to update an existing category (without changing the name attribute).
When you want to update an existing category : you'll instantiate the form with the category attribute to edit:
def category_update(category_id):
""" update the given category """
category = Category.query.get(category_id)
form = CategoryForm(request.form, category)
The main problem is I don't know how to access the existing category object in the validator which would let me exclude the edited object from the query.
Is there a way to do it? Thanks.
In the validation phase, you will have access to all the fields. So the trick here is to pass in the primary key into your edit form, e.g.
class CategoryEditForm(CategoryForm):
id = IntegerField(widget=HiddenInput())
Then, in the Unique validator, change the if-condition to:
check = self.model.query.filter(self.field == field.data).first()
if 'id' in form:
id = form.id.data
else:
id = None
if check and (id is None or id != check.id):
Although this is not a direct answer I am adding it because this question is flirting with being an XY Problem. WTForms primary job is to validate that the content of a form submission. While a decent case could be made that verifying that a field's uniqueness could be considered the responsibility of the form validator, a better case could be made that this is the responsibility of the storage engine.
In cases where I have be presented with this problem I have treated uniqueness as an optimistic case, allowed it to pass form submission and fail on a database constraint. I then catch the failure and add the error to the form.
The advantages are several. First it greatly simplifies your WTForms code because you do not have to write complex validation schemes. Secondly, it could improve your application's performance. This is because you do not have to dispatch a SELECT before you attempt to INSERT effectively doubling your database traffic.
The unique validator needs to use the new and the old data to compare first before checking if the data is unique.
class Unique(object):
...
def __call__(self, form, field):
if field.object_data == field.data:
return
check = DBSession.query(model).filter(field == data).first()
if check:
raise ValidationError(self.message)
Additionally, you may want to squash nulls too. Depending on if your truly unique or unique but allow nulls.
I use WTForms 1.0.5 and SQLAlchemy 0.9.1.
Declaration
from wtforms.validators import ValidationError
class Unique(object):
def __init__(self, model=None, pk="id", get_session=None, message=None,ignoreif=None):
self.pk = pk
self.model = model
self.message = message
self.get_session = get_session
self.ignoreif = ignoreif
if not self.ignoreif:
self.ignoreif = lambda field: not field.data
#property
def query(self):
self._check_for_session(self.model)
if self.get_session:
return self.get_session().query(self.model)
elif hasattr(self.model, 'query'):
return getattr(self.model, 'query')
else:
raise Exception(
'Validator requires either get_session or Flask-SQLAlchemy'
' styled query parameter'
)
def _check_for_session(self, model):
if not hasattr(model, 'query') and not self.get_session:
raise Exception('Could not obtain SQLAlchemy session.')
def __call__(self, form, field):
if self.ignoreif(field):
return True
query = self.query
query = query.filter(getattr(self.model,field.id)== form[field.id].data)
if form[self.pk].data:
query = query.filter(getattr(self.model,self.pk)!=form[self.pk].data)
obj = query.first()
if obj:
if self.message is None:
self.message = field.gettext(u'Already exists.')
raise ValidationError(self.message)
To use it
class ProductForm(Form):
id = HiddenField()
code = TextField("Code",validators=[DataRequired()],render_kw={"required": "required"})
name = TextField("Name",validators=[DataRequired()],render_kw={"required": "required"})
barcode = TextField("Barcode",
validators=[Unique(model= Product, get_session=lambda : db)],
render_kw={})
Looks like what you are looking for can easily be achieved with ModelForm which is built to handle forms that are strongly coupled with models (the category model in your case).
To use it:
...
from wtforms_components import Unique
from wtforms_alchemy import ModelForm
class CategoryForm(ModelForm):
name = TextField(u'name', [validators.Required(), Unique(Category, Category.name)])
It will verify unique values while considering the current value in the model. You can use the original Unique validator with it.
This worked for me, simple and easy:
Make sure that every time when a new row created in DB it must have unique name in colomn_name_in_db otherwise it will not work.
class SomeForm(FlaskForm):
id = IntegerField(widget=HiddenInput())
fieldname = StringField('Field name', validators=[DataRequired()])
...
def validate_fieldname(self, fieldname):
names_in_db = dict(Model.query.with_entities(Model.id,
Model.colomn_name_in_db).filter_by(some_filtes_if_needed).all())
if fieldname.data in names_in_db.values() and names_in_db[int(self.id)] != fieldname.data:
raise ValidationError('Name must be unique')