I'm struggling with some Django, where I want to make a custom MultiValueField combined with MultiWidget. I've read misc. tutorials, but it seem I'm missing something - most of them were quite old, which I suspect could be the reason.
I'm using Django 1.10.
Goal: Make a custom field that provides three dropdowns for a form. So far, no requirements to the contents of the dropdowns - first I just want to see them in my form :-)
I have a fields.py file, containing:
from django import forms
from widgets import MyCustomWidget
class MyCustomField(forms.MultiValueField):
widget = MyCustomWidget
def __init__(self, *args, **kwargs):
fields = (
forms.CharField(max_length=31),
forms.CharField(max_length=31),
forms.CharField(max_length=31),
)
super(MyCustomField, self).__init__(fields, *args, **kwargs)
def compress(self, data_list):
return "-".join(data_list)
And then there's a widgets.py, containing:
import re
from django import forms
class MyCustomWidget(forms.MultiWidget):
def __init__(self, attrs=None):
widgets = (
forms.widgets.Select(attrs=attrs, choices=[("1", "1")]),
forms.widgets.Select(attrs=attrs, choices=[("2", "2")]),
forms.widgets.Select(attrs=attrs, choices=[("3", "3")]),
)
super(MyCustomWidget, self).__init__(widgets, attrs)
def decompress(self, value):
if value:
return re.split(r"\-", value)
return [None, None, None]
forms.py:
from django.forms import ModelForm
from django import forms
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyCustomModel
fields = ("name")
name = forms.CharField(widget=MyCustomField)
This works fine when migrating, but I try to view the form, I get this error:
'MyCustomField' object has no attribute 'is_hidden'
I have tried to implement this attribute in MyCustomField, but then I get another error:
'MyCustomField' object has no attribute 'attrs'
These attributes should be provided by forms.MultiValueField, as far as I understand - thus I shouldn't need to write them myself.
In the template, I'm just using "{{ form }}" as I wan't to use Django's default layout.
I'm going nuts here and hope someone is able to help to the right path :-)
Kind regards,
Kasper
I believe your mistake is on the line where you set the name
name = forms.CharField(widget=MyCustomField). I haven't tested the codes thou.
from django.forms import ModelForm
from django import forms
class MyCustomForm(forms.ModelForm):
class Meta:
model = MyCustomModel
fields = ("name")
name = MyCustomField # This I believe is will fix your error
Related
I am trying to get a file upload form field working in Django and the part I am having problems with is dynamically changing the form field required attribute. I have tried using "self.fields['field_name'].required=True' in the init method of the form but that isn't working for me.
I have looked at Django dynamically changing the required property on forms but I don't want to build several custom models and a custom render function for one form as surely it must be easier than that.
The reason I am trying to do this is because when a django form validates and has errors it doesn't pass any uploaded files back to the browser form for reediting. It will pass text areas and text inputs that didn't validate back to the form for reediting but not file uploads. I thought if I made the file upload fields mandatory for the first time the record is created mandatory and for subsequent times make them optional. That is basically what I am trying to do.
So here is what I have been trying so far:
In forms.py
from django.forms import fields
from .widgets import PDFUploadWidget, PlainTextWidget
class WQPDFField(fields.Field):
widget = PDFUploadWidget
def widget_attrs(self, widget):
attrs = super().widget_attrs(widget)
attrs['label'] = self.label
return attrs
def clean(self, *args, **kwargs):
return super().clean(*args, **kwargs)
In widgets.py
from django.forms import widgets
from django.utils.safestring import mark_safe
# We subclass from HiddenInput because we want to suppress the printing
# of the label and prefer to print it ourselves.
class PDFUploadWidget(widgets.HiddenInput):
template_name = 'webquest_widgets/widgets/pdf_upload.html'
input_type = 'file'
def __init__(self, *args, **kwargs):
style = 'visibility:hidden'
attrs = kwargs.pop('attrs', None)
if attrs:
attrs['style'] = style
else:
attrs = {'style':style}
attrs['accept'] = '.pdf'
print (attrs)
super().__init__(attrs)
def get_context(self, name, value, attrs):
context = super().get_context(name, value, attrs)
return context
#property
def is_hidden(self):
return True
class Media:
css = { 'all': ( 'css/pdfupload.css', ) }
js = ('js/pdfupload.js', )
and finally in forms.py
class WorkWantedForm(forms.Form):
category = forms.ChoiceField(choices=CHOICES)
about = forms.CharField(label="About Yourself", widget=forms.Textarea())
static1 = WQStaticField(text="Enter either phone or email")
phone = forms.CharField(required=False)
email = forms.EmailField(required=False)
cv = WQPDFField(label="Upload CV")
supporting_document = WQPDFField(label="Supporting Document (optional)", required=False)
I am not sure how to pass the "required" attribute to the custom field class after the initialisation of the form but before rendering the form as HTML.
Original Title: cannot get import to work properly
I am trying to generate a function which will create a random alphanumeric number and make it as default value of a model field in django.
So for a single model I did like this:
# utils.py
def generate_random_unique_code_for_model():
from .models import mymodel
while 1:
code = random_code() #my custom function to generate random alphanumeric
try:
mymodel.objects.get(myfield=code)
except mymodel.DoesNotExist:
return code
#models.py
class mymodel(models.Model):
#other fields
myfield = models.CharField(default=generate_random_unique_code_for_model)
This code works fine, but now I have to provide similar function for another model, so to follow the DRY principle I am trying to make the model , fieldnames dynamic. So basically I am trying to accomplish from some_app.models import some_model inside my generate_random_unique_code_for_model function .
def get_model(location, model_name):
try:
module = __import__('.'.join(location), globals(), locals(), [model_name], -1)
model_instance = getattr(module, model_name)
except:
raise ImportError(_('Could not import %(model_name)s from %(location)s') % {'model_name': model_name,
'location': '.'.join(location)})
return model_instance
def generate_random_unique_code_for_model(location, model_name, field_name):
model_object = get_model(location, model_name)
kwargs = {field_name: ''}
while 1:
code = random_code()
kwargs[field_name] = code
try:
model_object.objects.get(**kwargs)
except model_object.DoesNotExist:
return code
#models.py
class mymodel_name(models.Model):
#other fields
myfield_name = models.CharField(default=generate_random_unique_code_for_model(['myapp_name', 'mymodel_name'], 'myfield_name'))
While debuggin, when I do dir(module) while debugging I don't see mymodel_name in the list. Any workarounds please?
The problem was default takes a callable function so whenever an instance of model is instantiated the default function is called. But since I called the function in the second case whenever the server is started and models are loaded it was trying to load model before the model class was created. So the problem comes down to pass a callable function with parameters to default which is not possible as of now. So what I did is this:
def make_random():
return generate_random_unique_code_for_model(['myapp_name', 'mymodel_name'], 'myfield_name')
class mymodel_name(models.Model):
#other fields
myfield_name = models.CharField(default=make_random)
I'm using django-autocomplete-light in a django admin application but i cant get choiches correctly filtered for a fk field with limit_choiches_to argument: I still get the entire queryset. here's the code:
# autocomplete_light.py
from django.db.models import Q
import autocomplete_light
from myapp.models import MyClass
from otherapp.models import Deps
class MyClassAutocomplete(autocomplete_light.AutocompleteModelBase):
""" MyClass autocomplete widget class """
choiches = MyModels.objects.filter(
Q(dpt__in=Deps.MAIN_DEPARTMENTS),
Q(user__is_active=True)
)
search_fields = ['^full_name', 'initials']
attrs = {'placeholder': 'Type a name'}
autocomplete_light.register(MyClass, MyClassAutocomplete)
# admin.py
class SampleModelAdminForm(forms.ModelForm):
class Meta:
link_attrs = {'cols': 105, 'rows': 3}
model = SampleModel
def __init__(self, *args, **kwargs):
super(SampleModelAdminForm, self).__init__(
*args, **kwargs
)
self.fields['my_fk'].widget = autocomplete_light.ChoiceWidget(
'MyClassAutocomplete'
)
I also tried to override choices_for_request method in AutocompleteModelBase subclass:
def choices_for_request(self):
return MyModels.objects.filter(
Q(dpt__in=Deps.MAIN_DEPARTMENTS),
Q(user__is_active=True)
)
By this way I have the filtered queryset, but I loose the autocomplete feature (for every word that I type, e.g. 'Es', it starts to show me the choiches from the A letter)
Anybody can help me with that?
thanks
Typo: choiches in
choiches = MyModels.objects.filter(
We have one application containing models.py which contains n no. of classes that inherits base class.We want to create form which dynamically takes value from user n saves in db but problem is that we want to use django form fields instead of django model forms.
As we know there are some fields missing in django forms such as PositiveIntegerField, CommaSeparetedIntegerFields etc. How can we achieve this using django form fields?
If we write follwing code in shell.
from djnago.db import models
mvar = models.PositiveIntegerFields()
from django import forms
fvar = forms.PositiveIntegerFields()
AttributeError: 'module' object has no attribute 'PositiveIntegerField'
forms.py
from django import forms
class ContextForm(forms.Form):
def __init__(self, rdict, *args, **kwargs):
super(ContextForm, self).__init__(*args, **kwargs)
for key in rdict.keys():
self.fields['%s' % str(key)] = getattr(forms,rdict.get(key))()
rdict = {'address': 'CharField','phone': 'CharField', 'Salary': 'PositiveIntegerField','first name': 'CharField','last name':'CharField'}
Looking at the source, all the field does is call the default form field with a keyword argument: min_value.
class PositiveIntegerField(IntegerField):
description = _("Positive integer")
def get_internal_type(self):
return "PositiveIntegerField"
def formfield(self, **kwargs):
defaults = {'min_value': 0}
defaults.update(kwargs)
return super(PositiveIntegerField, self).formfield(**defaults)
Therefore what you are looking for is merely
from django import forms
fvar = forms.IntegerField(min_value=0)
fvar.clean(-1)
# ValidationError: [u'Ensure this value is greater than or equal to 0.']
As for CommaSeparatedIntegerField, it looks like a CharField with some django.core.validators.validate_comma_separated_integer_list passed in.
f = forms.CharField(validators=[django.core.validators.validate_comma_separated_integer_list])
f.clean('1,2,3')
All this does is make sure the passed in string is '^[\d,]+$'. The field doesn't even do any python conversions... it doesn't really seem to save much time if just validates form input. Indeed, there's a comment that says "maybe move to contrib". Agreed..
Decided to look into this for fun. Here's a ModelForm generator that overrides model fields with new fields... It doesn't yet handle kwargs. It was just the first method I could think of to do this.. without looking into modelform generation itself. It constructs a regular ModelForm that modifies the form /after/ initialization.
MODEL_FIELD_MAP = {
models.IntegerField: forms.CharField,
# change all IntegerField to forms.CharField
}
def modelform_generator(mymodel):
class MyModelForm(forms.ModelForm):
class Meta:
model = mymodel
def __init__(self, *args, **kwargs):
super(MyModelForm, self).__init__(*args, **kwargs)
for name, form_field in self.fields.items():
try:
model_field = self._meta.model._meta.get_field_by_name(name)[0]
# is this a model field?
field_override = MODEL_FIELD_MAP.get(model_field.__class__)
# do we have this model field mapped to a form field?
if field_override:
self.fields[name] = field_override()
# set the form field to the target field class
except models.FieldDoesNotExist:
pass
return MyModelForm
I have a standard admin change form for an object, with the usual StackedInline forms for a ForeignKey relationship. I would like to be able to link each inline item to its corresponding full-sized change form, as the inline item has inlined items of its own, and I can't nest them.
I've tried everything from custom widgets to custom templates, and can't make anything work. So far, the "solutions" I've seen in the form of snippets just plain don't seem to work for inlines. I'm getting ready to try some DOM hacking with jQuery just to get it working and move on.
I hope I must be missing something very simple, as this seems like such a simple task!
Using Django 1.2.
There is a property called show_change_link since Django 1.8.
I did something like the following in my admin.py:
from django.utils.html import format_html
from django.core.urlresolvers import reverse
class MyModelInline(admin.TabularInline):
model = MyModel
def admin_link(self, instance):
url = reverse('admin:%s_%s_change' % (instance._meta.app_label,
instance._meta.module_name),
args=(instance.id,))
return format_html(u'Edit', url)
# … or if you want to include other fields:
return format_html(u'Edit: {}', url, instance.title)
readonly_fields = ('admin_link',)
The currently accepted solution here is good work, but it's out of date.
Since Django 1.3, there is a built-in property called show_change_link = True that addresses this issue.
This can be added to any StackedInline or TabularInline object. For example:
class ContactListInline(admin.TabularInline):
model = ContactList
fields = ('name', 'description', 'total_contacts',)
readonly_fields = ('name', 'description', 'total_contacts',)
show_change_link = True
The result will be something line this:
I had similar problem and I came up with custom widget plus some tweaks to model form. Here is the widget:
from django.utils.safestring import mark_safe
class ModelLinkWidget(forms.Widget):
def __init__(self, obj, attrs=None):
self.object = obj
super(ModelLinkWidget, self).__init__(attrs)
def render(self, name, value, attrs=None):
if self.object.pk:
return mark_safe(
u'<a target="_blank" href="../../../%s/%s/%s/">%s</a>' %\
(
self.object._meta.app_label,
self.object._meta.object_name.lower(),
self.object.pk, self.object
)
)
else:
return mark_safe(u'')
Now since widget for each inline need to get different object in constructor you can't just set it in standard way, but in Form's init method:
class TheForm(forms.ModelForm):
...
# required=False is essential cause we don't
# render input tag so there will be no value submitted.
link = forms.CharField(label='link', required=False)
def __init__(self, *args, **kwargs):
super(TheForm, self).__init__(*args, **kwargs)
# instance is always available, it just does or doesn't have pk.
self.fields['link'].widget = ModelLinkWidget(self.instance)
Quentin's answer above works, but you also need to specify fields = ('admin_link',)
There is a module for this purpose. Check out:
django-relatives
I think: args=[instance.id] should be args=[instance.pk]. It worked for me!