Wtforms, add a class to a form dynamically - python

is there a way i could send a form's (css) class from python?
For example:
class Company(Form):
companyName = TextField('Company Name', [validators.Length(min=3, max = 60)])
This renders a simple text field, but i want that text field to have the css class of .companyName, is that possible directly from python?
I know that i can put a id="companyName" directly from python, but not class.
Help.
Update:
I tried class_="companyName" and it did not work, i got:
__init__() got an unexpected keyword argument '_class'

Alternatively you can add the class in your template like this for jinja2:
{{ form.name(size=20, class_='input-small') }}

WTForms does not allow you to set display options (such as class name) in the field initialization. However, there are several ways to get around this:
If all of your fields should include a class name as well as an ID then just pass in each field's short_name to it when you render it:
<dl>
{% for field in form %}
<dt>{{field.label}}</dt>
<dd>{{field(class_=field.short_name)}}</dd>
{% endfor %}
</dl>
Create a custom widget mixin that provides the class name:
from wtforms.fields import StringField
from wtforms.widgets import TextInput
class ClassedWidgetMixin(object):
"""Adds the field's name as a class
when subclassed with any WTForms Field type.
Has not been tested - may not work."""
def __init__(self, *args, **kwargs):
super(ClassedWidgetMixin, self).__init__(*args, **kwargs)
def __call__(self, field, **kwargs):
c = kwargs.pop('class', '') or kwargs.pop('class_', '')
kwargs['class'] = u'%s %s' % (field.short_name, c)
return super(ClassedWidgetMixin, self).__call__(field, **kwargs)
# An example
class ClassedTextInput(ClassedWidgetMixin, TextInput):
pass
class Company(Form):
company_name = StringField('Company Name', widget=ClassedTextInput)

Use render_kw if using WTForms >= 2.1 :
submit = SubmitField(u'Block Submit Buttom', render_kw={"class": "btn btn-primary btn-block"})

In your template, try it
{{ form.companyName( **{'class': 'companyName'} ) }}

Related

Disabled field is considered for validation in WTForms and Flask

I have some fields in page disabled as for example:(using jinja2 templating system)
<html>
<body>
<form action="" method=POST>
{{ form.name(disabled=True) }}
{{ form.title }}
-- submit button --
</form>
</body>
</html>
Field is disabled in the form as expected.
In my views.py: On doing validate_on_submit() on form submit, it fails with validation error on 'name' field which is disabled. I was hoping that validation ignores disabled field. Is it the right behaviour? If so, can you please let know how to handle such a case?
Updated:
class TeamForm(wtf.Form):
name = wtf.TextField("Team Name", validators=[validators.Required()])
title = wtf.TextField("Title", validators=[validators.Required()])
This is actually an interesting problem, and the way WTForms solves it is intentionally something that requires explicitness, because it has to do with security and not allowing users to fake input.
So the intent is, that "managers" cannot edit the name, while "admins" can.
At first glance this seems obvious, just disable the field in HTML, and write your view like this:
def edit_team():
form = TeamForm(request.POST, obj=team)
if request.POST and form.validate():
form.populate_obj(team) # <-- This is the dangerous part here
return redirect('/teams')
return render('edit_team.html')
As written, this is a major security risk, because the disabled property in HTML forms is client-side only. Anyone with an HTML inspector (ie FireBug, webkit document inspector, etc) can remove this property, or someone could simply make a request like so:
POST /edit_team/7 HTTP/1.0
Content-Type: application/x-urlencoded
team=EVILTEAMNAME&title=foo
The issue then is of course, how do we gate this properly on the server-side, corresponding to the appropriate way of doing this? The correct approach with WTForms is to not have the field in the first place. There's a few ways to do this, one is to use form composition and have e.g. ManagerTeamForm and AdminTeamForm (sometimes this is better) but other times it's easier to use del to remove specific fields.
So here's how you would write your view, and not have the validation issues:
def edit_team():
form = TeamForm(request.POST, obj=team)
if user.role == 'manager':
del form.name
if request.POST and form.validate():
form.populate_obj(team)
return redirect('/teams')
return render('edit_team.html')
And a quick modification to the template:
<html>
<body>
<form action="" method=POST>
{% if 'name' in form %}
{{ form.name() }}
{% else %}
{{ team.name|e }}
{% endif %}
{{ form.title }}
-- submit button --
</form>
</body>
</html>
Some pieces of reference for wtforms best-practices:
WTForms 'Solving Specific Problems'
Dangers of Using forms as a backing store (WTForms google group) Post 1 / Post 2
StackOverflow: WTForms 'readonly' attribute
You need to make the name field optional when defining the form.
name = wtf.TextField("Team Name", validators=[validators.Optional()])
Then in your views, pass a variable called "role" and set it to either manager or admin depending on the user.
<form action="" method=POST>
{% if role == 'manager' % }
{{ form.name(disabled=True) }}
{% else % }
{{ form.name() }}
{{ form.title }}
-- submit button --
</form>
I defined my own validator for this problem:
from wtforms.validators import Optional
class OptionalIfDisabled(Optional):
def __call__(self, form, field):
if field.render_kw is not None and field.render_kw.get('disabled', False):
field.flags.disabled = True
super(OptionalIfDisabled, self).__call__(form, field)
And then I defined a new base for my forms:
from wtforms.form import Form
class BaseForm(Form):
def populate_obj(self, obj):
for name, field in self._fields.items():
if not field.flags.disabled:
field.populate_obj(obj, name)
Now every form can extend the BaseForm and disable fields like this:
from wtforms.fields import StringField, SubmitField
class TeamForm(BaseForm):
team = StringField(label='Team Name',
validators=[OptionalIfDisabled(), InputRequired()]
submit = SubmitField(label='Submit')
def __init__(self, *args, **kwargs):
super(TeamForm, self).__init__(*args, **kwargs)
# disable the fields if you want to
if some_condition:
self.team.render_kw = {'disabled': True}
After validation of the TeamForm, you can use populate_obj to copy the enabled form data in any object. It will ignore the disabled fields.
Create a custom validator
from wtforms.validators import Optional
class DisabledValidator(Optional):
"""
do nothing
"""
pass
Let's create a custom rule basing on the form.rule
from flask_admin.form.rules import Field
class EasyCustomFieldRule(Field):
def __init__(self, field_name, render_field='lib.render_field', field_args={}):
super(self.__class__, self).__init__(field_name, render_field)
self.extra_field_args = field_args
def __call__(self, form, form_opts=None, field_args={}):
field = getattr(form, self.field_name)
if self.extra_field_args.get('disabled'):
field.validators.append(DisabledValidator())
field_args.update(self.extra_field_args)
return super(self.__class__, self).__call__(form, form_opts, field_args)
Override write some functions of wtforms.form
from wtforms.form import Form
from wtforms.compat import iteritems
class BaseForm(Form):
"""
重写部分方法,以适应disabled的Field
"""
def validate(self):
"""
Validates the form by calling `validate` on each field, passing any
extra `Form.validate_<fieldname>` validators to the field validator.
"""
extra = {}
for name in self._fields:
inline = getattr(self.__class__, 'validate_%s' % name, None)
if inline is not None:
extra[name] = [inline]
return self.validate_(extra)
def validate_(self, extra_validators=None):
self._errors = None
success = True
for name, field in iteritems(self._fields):
is_disabled = False
for v in field.validators:
if isinstance(v, DisabledValidator):
field.flags.disabled = True
is_disabled = True
break
if is_disabled:
continue
if extra_validators is not None and name in extra_validators:
extra = extra_validators[name]
else:
extra = tuple()
if not field.validate(self, extra):
success = False
return success
def populate_obj(self, obj):
for name, field in self._fields.items():
if not field.flags.disabled:
field.populate_obj(obj, name)
set the form_base_class in your ModelView, and set the form_edit_rules or the form_create_rules with EasyCustomFieldRule
from flask_admin.contrib.sqla import ModelView
class MyTestModelView(ModelView):
...
form_base_class = BaseForm
...
form_edit_rules = (
EasyCustomFieldRule('column0', field_args={'disabled': True}),
'column1', 'column2'
)
Just testing...

How do I add a link from the Django admin page of one object to the admin page of a related object?

To deal with the lack of nested inlines in django-admin, I've put special cases into two of the templates to create links between the admin change pages and inline admins of two models.
My question is: how do I create a link from the admin change page or inline admin of one model to the admin change page or inline admin of a related model cleanly, without nasty hacks in the template?
I would like a general solution that I can apply to the admin change page or inline admin of any model.
I have one model, post (not its real name) that is both an inline on the blog admin page, and also has its own admin page. The reason it can't just be inline is that it has models with foreign keys to it that only make sense when edited with it, and it only makes sense when edited with blog.
For the post admin page, I changed part of "fieldset.html" from:
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{{ field.field }}
{% endif %}
to
{% if field.is_readonly %}
<p>{{ field.contents }}</p>
{% else %}
{% ifequal field.field.name "blog" %}
<p>{{ field.field.form.instance.blog_link|safe }}</p>
{% else %}
{{ field.field }}
{% endifequal %}
{% endif %}
to create a link to the blog admin page, where blog_link is a method on the model:
def blog_link(self):
return '%s' % (reverse("admin:myblog_blog_change",
args=(self.blog.id,)), escape(self.blog))
I couldn't find the id of the blog instance anywhere outside field.field.form.instance.
On the blog admin page, where post is inline, I modified part of "stacked.html" from:
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>
<span class="inline_label">{% if inline_admin_form.original %}
{{ inline_admin_form.original }}
{% else %}#{{ forloop.counter }}{% endif %}</span>
to
<h3><b>{{ inline_admin_formset.opts.verbose_name|title }}:</b>
<span class="inline_label">{% if inline_admin_form.original %}
{% ifequal inline_admin_formset.opts.verbose_name "post" %}
<a href="/admin/myblog/post/{{ inline_admin_form.pk_field.field.value }}/">
{{ inline_admin_form.original }}</a>
{% else %}{{ inline_admin_form.original }}{% endifequal %}
{% else %}#{{ forloop.counter }}{% endif %}</span>
to create a link to the post admin page since here I was able to find the id stored in the foreign key field.
I'm sure there is a better, more general way to do add links to admin forms without repeating myself; what is it?
Use readonly_fields:
class MyInline(admin.TabularInline):
model = MyModel
readonly_fields = ['link']
def link(self, obj):
url = reverse(...)
return mark_safe("<a href='%s'>edit</a>" % url)
# the following is necessary if 'link' method is also used in list_display
link.allow_tags = True
New in Django 1.8 : show_change_link for inline admin.
Set show_change_link to True (False by default) in your inline model, so that inline objects have a link to their change form (where they can have their own inlines).
from django.contrib import admin
class PostInline(admin.StackedInline):
model = Post
show_change_link = True
...
class BlogAdmin(admin.ModelAdmin):
inlines = [PostInline]
...
class ImageInline(admin.StackedInline):
# Assume Image model has foreign key to Post
model = Image
show_change_link = True
...
class PostAdmin(admin.ModelAdmin):
inlines = [ImageInline]
...
admin.site.register(Blog, BlogAdmin)
admin.site.register(Post, PostAdmin)
This is my current solution, based on what was suggested by Pannu (in his edit) and Mikhail.
I have a couple of top-level admin change view I need to link to a top-level admin change view of a related object, and a couple of inline admin change views I need to link to the top-level admin change view of the same object. Because of that, I want to factor out the link method rather than repeating variations of it for every admin change view.
I use a class decorator to create the link callable, and add it to readonly_fields.
def add_link_field(target_model = None, field = '', link_text = unicode):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = reverse_name + ' link'
cls.link = link
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + ['link']
return cls
return add_link
You can also pass a custom callable if you need to get your link text in some way than just calling unicode on the object you're linking to.
I use it like this:
# the first 'blog' is the name of the model who's change page you want to link to
# the second is the name of the field on the model you're linking from
# so here, Post.blog is a foreign key to a Blog object.
#add_link_field('blog', 'blog')
class PostAdmin(admin.ModelAdmin):
inlines = [SubPostInline, DefinitionInline]
fieldsets = ((None, {'fields': (('link', 'enabled'),)}),)
list_display = ('__unicode__', 'enabled', 'link')
# can call without arguments when you want to link to the model change page
# for the model of an inline model admin.
#add_link_field()
class PostInline(admin.StackedInline):
model = Post
fieldsets = ((None, {'fields': (('link', 'enabled'),)}),)
extra = 0
Of course none of this would be necessary if I could nest the admin change views for SubPost and Definition inside the inline admin of Post on the Blog admin change page without patching Django.
I think that agf's solution is pretty awesome -- lots of kudos to him. But I needed a couple more features:
to be able to have multiple links for one admin
to be able to link to model in different app
Solution:
def add_link_field(target_model = None, field = '', app='', field_name='link',
link_text=unicode):
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = app or instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe("<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = reverse_name + ' link'
setattr(cls, field_name, link)
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + \
[field_name]
return cls
return add_link
Usage:
# 'apple' is name of model to link to
# 'fruit_food' is field name in `instance`, so instance.fruit_food = Apple()
# 'link2' will be name of this field
#add_link_field('apple','fruit_food',field_name='link2')
# 'cheese' is name of model to link to
# 'milk_food' is field name in `instance`, so instance.milk_food = Cheese()
# 'milk' is the name of the app where Cheese lives
#add_link_field('cheese','milk_food', 'milk')
class FoodAdmin(admin.ModelAdmin):
list_display = ("id", "...", 'link', 'link2')
I am sorry that the example is so illogical, but I didn't want to use my data.
I agree that its hard to do template editing so, I create a custom widget to show an anchor on the admin change view page(can be used on both forms and inline forms).
So, I used the anchor widget, along with form overriding to get the link on the page.
forms.py:
class AnchorWidget(forms.Widget):
def _format_value(self,value):
if self.is_localized:
return formats.localize_input(value)
return value
def render(self, name, value, attrs=None):
if not value:
value = u''
text = unicode("")
if self.attrs.has_key('text'):
text = self.attrs.pop('text')
final_attrs = self.build_attrs(attrs,name=name)
return mark_safe(u"<a %s>%s</a>" %(flatatt(final_attrs),unicode(text)))
class PostAdminForm(forms.ModelForm):
.......
def __init__(self,*args,**kwargs):
super(PostAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance',None)
if instance.blog:
href = reverse("admin:appname_Blog_change",args=(instance.blog))
self.fields["link"] = forms.CharField(label="View Blog",required=False,widget=AnchorWidget(attrs={'text':'go to blog','href':href}))
class BlogAdminForm(forms.ModelForm):
.......
link = forms..CharField(label="View Post",required=False,widget=AnchorWidget(attrs={'text':'go to post'}))
def __init__(self,*args,**kwargs):
super(BlogAdminForm, self).__init__(*args, **kwargs)
instance = kwargs.get('instance',None)
href = ""
if instance:
posts = Post.objects.filter(blog=instance.pk)
for idx,post in enumerate(posts):
href = reverse("admin:appname_Post_change",args=(post["id"]))
self.fields["link_%s" % idx] = forms..CharField(label=Post["name"],required=False,widget=AnchorWidget(attrs={'text':post["desc"],'href':href}))
now in your ModelAdmin override the form attribute and you should get the desired result. I assumed you have a OneToOne relationship between these tables, If you have one to many then the BlogAdmin side will not work.
update:
I've made some changes to dynamically add links and that also solves the OneToMany issue with the Blog to Post hope this solves the issue. :)
After Pastebin:
In Your PostAdmin I noticed blog_link, that means your trying to show the blog link on changelist_view which lists all the posts. If I'm correct then you should add a method to show the link on the page.
class PostAdmin(admin.ModelAdmin):
model = Post
inlines = [SubPostInline, DefinitionInline]
list_display = ('__unicode__', 'enabled', 'blog_on_site')
def blog_on_site(self, obj):
href = reverse("admin:appname_Blog_change",args=(obj.blog))
return mark_safe(u"<a href='%s'>%s</a>" %(href,obj.desc))
blog_on_site.allow_tags = True
blog_on_site.short_description = 'Blog'
As far as the showing post links on BlogAdmin changelist_view you can do the same as above. My earlier solution will show you the link one level lower at the change_view page where you can edit each instance.
If you want the BlogAdmin page to show the links to the post in the change_view page then you will have to include each in the fieldsets dynamically by overriding the get_form method for class BlogAdmin and adding the link's dynamically, in get_form set the self.fieldsets, but first don't use tuples to for fieldsets instead use a list.
Based on agfs and SummerBreeze's suggestions, I've improved the decorator to handle unicode better and to be able to link to backwards-foreignkey fields (ManyRelatedManager with one result). Also you can now add a short_description as a list header:
from django.core.urlresolvers import reverse
from django.core.exceptions import MultipleObjectsReturned
from django.utils.safestring import mark_safe
def add_link_field(target_model=None, field='', app='', field_name='link',
link_text=unicode, short_description=None):
"""
decorator that automatically links to a model instance in the admin;
inspired by http://stackoverflow.com/questions/9919780/how-do-i-add-a-link-from-the-django-admin-page-of-one-object-
to-the-admin-page-o
:param target_model: modelname.lower or model
:param field: fieldname
:param app: appname
:param field_name: resulting field name
:param link_text: callback to link text function
:param short_description: list header
:return:
"""
def add_link(cls):
reverse_name = target_model or cls.model.__name__.lower()
def link(self, instance):
app_name = app or instance._meta.app_label
reverse_path = "admin:%s_%s_change" % (app_name, reverse_name)
link_obj = getattr(instance, field, None) or instance
# manyrelatedmanager with one result?
if link_obj.__class__.__name__ == "RelatedManager":
try:
link_obj = link_obj.get()
except MultipleObjectsReturned:
return u"multiple, can't link"
except link_obj.model.DoesNotExist:
return u""
url = reverse(reverse_path, args = (link_obj.id,))
return mark_safe(u"<a href='%s'>%s</a>" % (url, link_text(link_obj)))
link.allow_tags = True
link.short_description = short_description or (reverse_name + ' link')
setattr(cls, field_name, link)
cls.readonly_fields = list(getattr(cls, 'readonly_fields', [])) + \
[field_name]
return cls
return add_link
Edit: updated due to link being gone.
Looking through the source of the admin classes is enlightening: it shows that there is an object in context available to an admin view called "original".
Here is a similar situation, where I needed some info added to a change list view: Adding data to admin templates (on my blog).

Insert Header in Django form

I would like to insert a header in a Django form. I have the following fields:
Name
Price
Optional - Size
Optional - Color
But instead of having the "Optional - " in every optional item, I would like a header, so the form looks like this:
Name
Example
Optional fields:
Size
Color
(This is pseudocode but should be easy to illustrate my point)
I know i can render each individual form field in the HTML, but I would like to still use the {{ form.as_p }} (so I don't have to change the markup for every new field I want to add). Is there any way to define a Django property for the form so I can preserve the form.as_p and still have this additional header (and more headers for more sections of fields)?
Thank you
I made it with JQuery.
It is very simple.
$('#id_size').parent().parent().before('<h1>optional</h1>');
Well, for lack of a better option, I solved this problem by rendering the form's individual fields instead of the whole form.
Then I only have to do:
<div class="fieldWrapper">
(...Field 1...)
</div>
<h1>My Own Header</h1>
<div class="fieldWrapper">
(...Field 2...)
</div>
I think Django hasn't such functionality but django-uni-form has :) This is an example from uni-form documentation with small changes:
from uni_form.helper import FormHelper
from uni_form.layout import Layout, Fieldset
class ExampleForm(forms.Form):
[...]
def __init__(self, *args, **kwargs):
self.helper = FormHelper()
self.helper.layout = Layout(
Fieldset(
'name',
'price'
),
Fieldset(
'Optional',
'size',
'color'
),
ButtonHolder(
Submit('submit', 'Submit', css_class='button white')
)
)
return super(ExampleForm, self).__init__(*args, **kwargs)
Quite an outdated post, but I spent several hours in finding a solution, so I would like to share it. Hope it's useful.
In your apps directory create a file named widgets.py, with following content, to extend the basic Widget class from Django:
# widgets.py
from django.forms.widgets import Widget
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
class HeaderWidget(Widget):
def __init__(self, attrs=None, label=None, tag='h1'):
self.label = label
self.tag = tag
super(HeaderWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
# change blocks/ according to your template path. In this case was templates/block
widget = render_to_string("blocks/header-form-field.html", {
'tag' : self.tag,
'label' : self.label,
})
return widget
Then create a file customfileds.py . Here we extend the Field Class with mostly nothing. The important thing is to avoid field validation, ase we have no input.
from django import forms
from django.forms.fields import Field
from . import widgets
class HeaderField(Field):
widget = widgets.HeaderWidget
# This is to avoid validation
validators = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
def clean(self, value=''):
"""
It seem the clean method in mandatory when creating a Field class.
Jaust return value without validation
"""
return value
Then create the html template. Put it in a path that matches your
widget = render_to_string("blocks/header-form-field.html"
#header-form-field.html
<{{ tag }} class="your-custom-class">{{ label }}</{{ tag }}>
Now you can add to your forms any HeaderField you need, like that:
class MyForm(GenericForm):
header1 = customfileds.HeaderField(label=_(""),required=False,label_suffix='',
widget=widgets.HeaderWidget(label=_("My custom Label text here"),tag="h3"))
field 1 = whatever input field you have...
Here you can find same code in a gita page
https://gitlab.com/MaadiX/header-field-for-django-forms/blob/master/README.md

Django(trunk) and class based generic views: one form's initial data appearing in another one's

I've run into a strange problem where data seems to persist accross different views and requests until the server gets restarted.
I've managed to reduce the issue to the following code:
# foobar/models.py
from django.db import models
class Foo(models.Model):
bug = models.CharField(max_length=10)
# foobar/forms.py
from django import forms
from foobar.models import Foo
class CreateForm(forms.ModelForm):
class Meta:
model = Foo
class UpdateForm(forms.ModelForm):
class Meta:
model = Foo
def __init__(self, *args, **kwargs):
kwargs.setdefault('initial', {})
kwargs['initial'].update({'bug': 'WHY??'})
super(UpdateForm, self).__init__(*args, **kwargs)
# foobar/views.py
from django.views.generic.edit import CreateView, UpdateView
from foobar.forms import CreateForm, UpdateForm
from foobar.models import Foo
class FooCreateView(CreateView):
form_class = CreateForm
template_name = 'foobar/foo_form.html'
create = FooCreateView.as_view()
class FooUpdateView(UpdateView):
form_class = UpdateForm
template_name = 'foobar/foo_form.html'
queryset = Foo.objects.all()
update = FooUpdateView.as_view()
# foobar/urls.py
from django.conf.urls.defaults import *
urlpatterns = patterns('foobar.views',
('^$', 'create'),
(r'^(?P<pk>\d+)/$', 'update'),
)
You should also probably add a template (in foobar/templates/foo_form.html for example):
<form action="" method="post">
{{ form.as_p }}
<input type="submit" />
{% csrf_token %}
</form>
To reproduce, do the following:
Add the foobar app to settings.INSTALLED_APPS
Run syncdb
Add foobar.urls to your root urlconf
Navigate to /foobar/ (the actual URL will depend on your root urlconf)
Submit the form (thus creating a new Foo object)
Navigate to /foobar/1/. Notice that the form field is prepopulated (this is expected)
Navigate to /foobar/. Notice the form field is still populated (this is not expected).
Is this a bug or am I doing something I shouldn't be (or maybe both...)?
-- EDIT --
In forms.py, if I replace the update call by this:
kwargs['initial']['bug'] = 'WHY???'
Then the problem is still there.
Commenting out the line removes the problem (but then the form has no initial data, obviously).
Because you're mutating the kwargs that are passed in, which come from class-level properties in the view class.
Instead, copy them and update the copy:
initial_defaults = {'bug': 'no'}
initial_defaults.update(kwargs.get('initial', {}))
defaults = kwargs.copy()
defaults['initial'] = initial_defaults
You might want to specify Django-1.3 development, generic class view doesn't exist in Django 1.2.5. In your forms.py file, can you comment the following lines and try again:
class UpdateForm(forms.ModelForm):
class Meta:
model = Foo
def __init__(self, *args, **kwargs):
#kwargs.setdefault('initial', {})
#kwargs['initial'].update({'bug': 'WHY??'})
super(UpdateForm, self).__init__(*args, **kwargs)

Model limit_choices_to={'user': user}

I went to all the documentation, also I went to the IRC channel (BTW a great community) and they told me that is not possible to create a model and limit choices in a field where the 'current user' is in a ForeignKey.
I will try to explain this with an example:
class Project(models.Model):
name = models.CharField(max_length=100)
employees = models.ManyToManyField(Profile, limit_choices_to={'active': '1'})
class TimeWorked(models.Model):
project = models.ForeignKey(Project, limit_choices_to={'user': user})
hours = models.PositiveIntegerField()
Of course that code doesn't work because there is no 'user' object, but that was my idea and I was trying to send the object 'user' to the model to just limit the choices where the current user has projects, I don't want to see projects where I'm not in.
Thank you very much if you can help me or give me any advice, I don't want to you write all the app, just a tip how to deal with that. I have 2 days with this in my head and I can't figure it out :(
UPDATE: The solution is here: http://collingrady.wordpress.com/2008/07/24/useful-form-tricks-in-django/ sending request.user to a model.
This limiting of choices to current user is a kind of validation that needs to happen dynamically in the request cycle, not in the static Model definition.
In other words: at the point where you are creating an instance of this model you will be in a View and at that point you will have access to the current user and can limit the choices.
Then you just need a custom ModelForm to pass in the request.user to, see the example here:
http://collingrady.wordpress.com/2008/07/24/useful-form-tricks-in-django/
from datetime import datetime, timedelta
from django import forms
from mysite.models import Project, TimeWorked
class TimeWorkedForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
super(ProjectForm, self).__init__(*args, **kwargs)
self.fields['project'].queryset = Project.objects.filter(user=user)
class Meta:
model = TimeWorked
then in your view:
def time_worked(request):
form = TimeWorkedForm(request.user, request.POST or None)
if form.is_valid():
obj = form.save()
# redirect somewhere
return render_to_response('time_worked.html', {'form': form})
Model itself doesn't know anything about current user but you can give this user in a view to the form which operates models objects (and in form reset choices for necessary field).
If you need this on admin site - you can try raw_id_admin along with django-granular-permissions (http://code.google.com/p/django-granular-permissions/ but I couldn't rapidly get it working on my django but it seems to be fresh enough for 1.0 so...).
At last, if you heavily need a selectbox in admin - then you'll need to hack django.contrib.admin itself.
Using class-based generic Views in Django 1.8.x / Python 2.7.x, here is what my colleagues and I came up with:
In models.py:
# ...
class Proposal(models.Model):
# ...
# Soft foreign key reference to customer
customer_id = models.PositiveIntegerField()
# ...
In forms.py:
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.forms import ModelForm, ChoiceField, Select
from django import forms
from django.forms.utils import ErrorList
from django.core.exceptions import ValidationError
from django.utils.translation import ugettext as _
from .models import Proposal
from account.models import User
from customers.models import customer
def get_customers_by_user(curUser=None):
customerSet = None
# Users with userType '1' or '2' are superusers; they should be able to see
# all the customers regardless. Users with userType '3' or '4' are limited
# users; they should only be able to see the customers associated with them
# in the customized user admin.
#
# (I know, that's probably a terrible system, but it's one that I
# inherited, and am keeping for now.)
if curUser and (curUser.userType in ['1', '2']):
customerSet = customer.objects.all().order_by('company_name')
elif curUser:
customerSet = curUser.customers.all().order_by('company_name')
else:
customerSet = customer.objects.all().order_by('company_name')
return customerSet
def get_customer_choices(customerSet):
retVal = []
for customer in customerSet:
retVal.append((customer.customer_number, '%d: %s' % (customer.customer_number, customer.company_name)))
return tuple(retVal)
class CustomerFilterTestForm(ModelForm):
class Meta:
model = Proposal
fields = ['customer_id']
def __init__(self, user=None, *args, **kwargs):
super(CustomerFilterTestForm, self).__init__(*args, **kwargs)
self.fields['customer_id'].widget = Select(choices=get_customer_choices(get_customers_by_user(user)))
# ...
In views.py:
# ...
class CustomerFilterTestView(generic.UpdateView):
model = Proposal
form_class = CustomerFilterTestForm
template_name = 'proposals/customer_filter_test.html'
context_object_name = 'my_context'
success_url = "/proposals/"
def get_form_kwargs(self):
kwargs = super(CustomerFilterTestView, self).get_form_kwargs()
kwargs.update({
'user': self.request.user,
})
return kwargs
In templates/proposals/customer_filter_test.html:
{% extends "base/base.html" %}
{% block title_block %}
<title>Customer Filter Test</title>
{% endblock title_block %}
{% block header_add %}
<style>
label {
min-width: 300px;
}
</style>
{% endblock header_add %}
{% block content_body %}
<form action="" method="POST">
{% csrf_token %}
<table>
{{ form.as_table }}
</table>
<input type="submit" value="Save" class="btn btn-default" />
</form>
{% endblock content_body %}
I'm not sure that I fully understand exactly what you want to do, but I think that there's a good chance that you'll get at least part the way there using a custom Manager. In particular, don't try to define your models with restrictions to the current user, but create a manager that only returns objects that match the current user.
Hmmm, I don't fully understand your question. But if you can't do it when you declare the model maybe you can achieve the same thing with overriding methods of the class of objects where you "send" the user object, maybe start with the constructor.
Use threadlocals if you want to get current user that edits this model. Threadlocals middleware puts current user into process-wide variable. Take this middleware
from threading import local
_thread_locals = local()
def get_current_user():
return getattr(getattr(_thread_locals, 'user', None),'id',None)
class ThreadLocals(object):
"""Middleware that gets various objects from the
request object and saves them in thread local storage."""
def process_request(self, request):
_thread_locals.user = getattr(request, 'user', None)
Check the documentation on how to use middleware classes. Then anywhere in code you can call
user = threadlocals.get_current_user

Categories

Resources