I am creating a form that uses MultipleChoiceField. The values for this field are derived from another model. This method works fine, however, I am noticing (on the production server) that when I add a new item to the model in question (NoticeType), the form does not dynamically update. I have to restart the server for the new item to show up on my MultipleChoiceField.
Any changes to the NoticeType model (editing items or creating new ones) do not propagate to the form. After I restart the production server, the updates appear.
Any ideas why this might be ? The relevant portion of the form is below. Thanks.
from django import forms
from django.contrib.auth.models import User
from notification.models import NoticeType
class EditUserProfileForm(forms.Form):
CHOICES = []
for notice in NoticeType.objects.all():
CHOICES.append( (notice.label,notice.display) )
notifications = forms.MultipleChoiceField(
label="Email Notifications",
required=False,
choices=( CHOICES ),
widget=forms.CheckboxSelectMultiple,)
Although mherren is right that you can fix this problem by defining your choices in the __init__ method, there is an easier way: use the ModelMultipleChoiceField which is specifically designed to take a queryset, and updates dynamically.
class EditUserProfileForm(forms.Form):
notifications = forms. ModelMultipleChoiceField(
label="Email Notifications",
required=False,
queryset = NoticeType.objects.all(),
widget=forms.CheckboxSelectMultiple)
My hunch is that the class definition is only being processed once on load rather than for each instantiation. Try adding the CHOICES computation to the init method like so:
def __init__(self, *args, **kwargs):
super(self.__class__, self).__init__(*args, **kwargs)
CHOICES = []
for notice in NoticeType.objects.all():
CHOICES.append( (notice.label, notice.display) )
self.fields['notifications'].choices = CHOICES
Related
I've seen a similar question, alas it has not been answered.
I have an app that features Entries (like blog entries) which include a part called SubEntry. I want the users to be able to report SubEntries (i.e. press the button 'report', fill some fields and the application sends an email to admins, saving the report in db is nice to have):
The flow should be like that: at the view EntryDetails (url: /entry/entry-title/) the user may click on the SubEntry part. The modal opens and the subentry is visualized in the modal as enlarged, with a button/link underneath 'Report the SubEntry'. Then it's possible to click on the 'Report the SubEntry' button and two fields appear - reason of reporting and contact detail of the reporter (here I am just toggling the visibility of the fields). I manage to display the form (with get overriden - overriding get_form_kwargs causes the error No Entry with that title) but either the Entry or its attributes are not displayed...
My questions are:
1) is creating a model for Reporting (ReportSubEntry) a decent approach?
2) I can't seem to pass the needed variable (an Entry object that is to be a ForeignKey for a SubEntry object that is being created) from CreateReport view to the report_subentry.html.
any thoughts, advice? Python 3.5, Django 1.10
models.py:
class ReportSubentry(models.Model):
Entry = models.ForeignKey('Entry')
details = models.CharField(max_length=100)
contact = models.EmailField()
forms.py:
class ReportEntryForm(forms.ModelForm):
class Meta:
model = ReportSubEntry
fields = ['details', 'contact', 'project']
views.py:
class CreateReport(CreateView):
model = ReportSubEntry
form_class = ReportSubEntryForm
template_name = 'understand/report_subentry.html'
# tried two methods to pass the variables:
def get(self, request, *args, **kwargs):
self.object = None
title = kwargs.get('title')
kwargs['entry'] = get_object_or_404(Entry, title=title)
return super(CreateReport, self).get(request, **kwargs)
def get_form_kwargs(self, **kwargs):
title = kwargs.get('title')
kwargs['entry'] = get_object_or_404(Entry, title=title)
return kwargs
The current model that you are using ReportSubEntry is perfect and there is no need to change it.
In your forms.py ReportEntryForm you have to use relatedfields to be able to correctly serialize the data. There is no need to override anything. When user clicks on report the sub entry you have to pass the pk of Entry model as it is required to know which entry is reported. I am assuming that since you are successfully displaying the entries pk of those are present. When you receive the pk with other two fields you get the corresponding entry for pk and then pass the object to ReportSubentry.objects.create method.
The reportentry form should not contain foreign key. You have two choices for that. First is remove that field and pass the pk of entry from frontend using ajax calls or use javascript to add a disabled input field which contains pk of entry when user clicks on report subentry.
Ok, so I've solved this issue.
The only solution that worked for me was overriding the get method of the ReportSubentry without calling the get method of the superclass:
def get(self, request, *args, **kwargs):
self.object = None
title = kwargs.get('title')
entry = get_object_or_404(Entry, title=title)
context_data = self.get_context_data()
context_data.update(entry=entry)
return self.render_to_response(context_data)
Please feel free to discuss it.
I'm becoming increasingly bewildered by the range of answers on offer to the seemingly simple problem of adding custom fields to the django-registration register form/flow. This should be a default, documented aspect of the package (not to sound ungrateful, just that it is such a well-equipped package), but solutions to the problem are dizzying.
Can anyone give me the most simple solution to getting UserProfile model data included in the default registration register page?
Update:
I eventually used Django Registration's own signals to give me this hacky fix. It is particularly ugly because, I had to use try on the POST attribute dealing with my Boolean since I found that the checkbox returned nothing if left empty.
Would appreciate any advice on improving this, or best practice.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)
Thanks
What you have looks like a workable approach.
I've looked through the django-registration code, and based on the following comments in the register view I've come up with another solution. I'm not totally sure this is cleaner, but if you aren't a fan of signals this is good. This also provides a much easier avenue if you intend to make more customizations.
# from registration.views.register:
"""
...
2. The form to use for account registration will be obtained by
calling the backend's ``get_form_class()`` method, passing the
``HttpRequest``. To override this, see the list of optional
arguments for this view (below).
3. If valid, the form's ``cleaned_data`` will be passed (as
keyword arguments, and along with the ``HttpRequest``) to the
backend's ``register()`` method, which should return the new
``User`` object.
...
"""
You could create a custom backend and override those mentioned methods:
# extend the provided form to get those fields and the validation for free
class CustomRegistrationForm(registration.forms.RegistrationForm):
receive_email = forms.BooleanField(initial=True, required=False)
# again, extend the default backend to get most of the functionality for free
class RegistrationBackend(registration.backends.default.DefaultBackend):
# provide your custom form to the registration view
def get_form_class(self, request):
return CustomRegistrationForm
# replace what you're doing in the signal handler here
def register(self, request, **kwargs):
new_user = super(RegistrationBackend, self).register(request, **kwargs)
# do your profile stuff here
# the form's cleaned_data is available as kwargs to this method
profile = new_user.userprofile
# use .get as a more concise alternative to try/except around [] access
profile.receive_email = kwargs.get('receive_email', False)
profile.save()
return new_user
To use the custom backend, you can then provide separate urls. Before including the default urls, write 2 confs that point at your custom backend. Urls are tested in the order defined, so if you define these two before including the defaults, these two will capture before the default ones are tested.
url(r'^accounts/activate/(?P<activation_key>\w+)/$',
activate,
{'backend': 'my.app.RegistrationBackend'},
name='registration_activate'),
url(r'^accounts/register/$',
register,
{'backend': 'my.app.RegistrationBackend'},
name='registration_register'),
url(r'^accounts/', include('registration.backends.default.urls')),
The docs actually describe all this, but they aren't particularly accessible (no readthedocs). They are all included in the project, and I was browsing them here.
I eventually used Django Registration's own signals to give me this fix.
I will clean up the try/except flow at some point. dokkaebi also points out above that I might be able to assess the request.GET parameters for when a checkbox is left empty.
My app / models.py
from registration.signals import user_registered
from django.dispatch import receiver
class UserProfile(models.Model):
user = models.OneToOneField(User)
event_commitments = models.ManyToManyField(Event, null=True, blank=True)
receive_email = models.BooleanField(default=True)
#receiver(user_registered)
def registration_active_receive_email(sender, user, request, **kwargs):
user_id = user.userprofile.id
user = UserProfile.objects.get(pk=user_id)
try:
if request.POST['receive_email']:
pass
except:
user.receive_email = False
user.save()
Registration app / forms.py
class RegistrationForm(forms.Form):
# default fields here, followed by my custom field below
receive_email = forms.BooleanField(initial=True, required=False)
In my django projects I have 2 related models "Vehicle"(parent model) and ParamConf(child model). ParamConf has field "program" and I want to fix some wrong values in it.
(admin.py):
class ParamConfFormSet(BaseInlineFormSet):
def clean(self):
super(ParamConfFormSet, self).clean()
for form in self.forms:
if hasattr(form, 'cleaned_data') and 'program' in form.cleaned_data:
program = form.cleaned_data['program'].lower() # <<< I want to save this changed value
form.cleaned_data['program'] = program # <<< but this doesn't work :^(
class ParamConfInline(admin.TabularInline):
model = models.ParamConf
formset = ParamConfFormSet
class VehicleAdminForm(forms.ModelForm):
class Meta:
model = models.Vehicle
class VehicleAdmin(admin.ModelAdmin):
inlines = [ ParamConfInline, ]
form = VehicleAdminForm
I even wrote save() method for ParamConf, but django doesn't want to call it after saving a Vehicle form(Vehicle's save method is alright).
Django emits save signals for inline parameters if field values actually changed (and I was trying to save form without editing fields). Sorry it's my fault.
I know that the question is very old, but someone else may be still looking for the answer...Saving a inline even if it was not changed:
Class EntityInline(admin.TabularInline):
extra = 0
model = Entity
form = AlwaysChangedModelForm
Let's assume that I have following models:
class ScoutBook(models.Model):
troop = models.ForeignKey('Dictionary', limit_choices_to={'type' : 'Troop'}, related_name='+', blank=True, null=True)
class Dictionary(models.Model):
name = models.CharField(max_length=CHAR_FIELD_MAX_LEN, verbose_name="Nazwa")
active = models.BooleanField(verbose_name="Aktywny")
type = models.CharField(max_length=CHAR_FIELD_MAX_LEN, choices=DICTIONARY_CHOICES)
and I want to implement following logic:
when creating ScoutBook allow users to select only active troops, and when editing allow to select active troops or allow user to leave value unchanged (even if the troop is inactive). If I use limit_choices_to = {..., 'active' = True} troop that is inactive is absent from combo box in django admin.
So to be clear: let's assume that there are four troops in this system: Troop1, Troop2 and InactiveTroop, InactiveTroop2. On model creation I would like user to be able to choose Troop1 and Troop2. If model has troop field set to InactiveTroop2, I would like user to be able to choose between InactiveTroop2, Troop1 and Troop2.
I was looking at the django forms api and I didn't found obvious way do this. Moreover, in the application I'm developing there will be many such fields and many such models --- so solution must be pain free. I would rather not create new Form class for every model. I will be using mostly django admin to enable editing the database, and some read only views that will just list entries.
Ideally I would like to encapsulate this functionality in some custom field --- but fields have access to model instance on validate and save phase --- so I dont have access to it when I produce formfield.
This sounds like something you want to do in a form, not in the object itself. Create a ModelForm and override the ModelChoiceField like this:
from django import forms
class ScoutBookForm(forms.ModelForm):
troop = forms.ModelChoiceField(queryset=Troop.objects.filter(active=True))
class Meta:
model = ScoutBook
You can also override the clean method of ScoutBook to ensure it cannot ever be saved with an inactive Troop, though that may have some unintended consequences (e.g., you wouldn't be able to update a ScoutBook in the admin if the troop had gone inactive at some point in the past).
Well I had to hook into ModelForm creation. Attached Form inspects it's fields and if specific conditions are met it replaces model field queryset.
class DictionayModelForm(ModelForm):
def __init__(self, *largs, **kwargs):
super(DictionayModelForm, self).__init__(*largs, **kwargs)
if self.instance and self.instance.pk is not None:
for f in self.instance._meta.fields:
if isinstance(f, models.ForeignKey) and issubclass(f.rel.to, Dictionary):
model_field = self.fields[f.name]
value = getattr(self.instance, f.name, None)
if value and value not in model_field.choices:
model_field.queryset = Dictionary.objects.filter(Q(**f.rel.limit_choices_to) | Q(id = value.id))
I can't find a single example of anyone doing this apart from this example, which doesn't help me other than to know where the code needs to sit.
How to prepopulate UserProfile fields in the Django admin?
so this is my code
class QuoteMaterial(models.Model):
name = models.CharField(_('name'), max_length=255)
content = models.TextField(_('content'),
help_text=_('A static priced item used when doing a job. Selectable when creating a quote. '))
price = models.DecimalField(_('price'), max_digits=6, help_text="not sure if this is before or after VAT yet", decimal_places=2, default="0.00")
def get_companies():
return CompanyProfile.objects.filter(user=request.user)
company = models.ForeignKey(CompanyProfile, default=get_companies)
If its not obvious, im trying in the admin section to populate a dropdown with the available companies that belong to the user that is logged in.
my problem is that i dont know how to pass the request object to "get_companies". anyone know of any examples.
You will have to do this overriding in your admin class that extends the ModelAdmin, not in your class that extends models.Model. Specifically, you need to override formfield_for_foreignkey.
From the docs:
class MyModelAdmin(admin.ModelAdmin):
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == "car":
kwargs["queryset"] = Car.objects.filter(owner=request.user)
return super(MyModelAdmin, self).formfield_for_foreignkey(db_field, request, **kwargs)
For your case, it would seem like:
if db_field.name == "company":
kwargs['queryset'] = request.user.company_set.all()
You're mixing up terms.
"Prepopulating" means to fill in a field from another field. It's not how you filter things for the admin popups, since you aren't actually setting the field, but simply limiting choices and letting the user set the field from those.
Aditionally, the default value for a field needs to be a constant, since this is passed down to the database, which can't use a query to set a default.
What you really want is something like the limit_choices_to (docs) parameter for your ForeignKey, but even then, you can't use request for this; it has to work using fields in the model. The reason for this is that, if you based it on the user, then some users would be unable to select the current value set by another user. You don't want company changing itself when the user just wants to change content, for example, just because user doesn't yield the current company in the filter.