I have a form field with autocomplete (using django-autocomplete-light app and Select2 widget), which serve as a search filter and it works as expected.
When I submit the form and search results are listed, I would like to set this form field initial value to previously submitted value - so the user can adjust some of the search parameters instead of setting up all search filters from scratch.
This form field will be used to choose one of the ~10000 values, so I need it to load values on-demand. As the form field is not prepopulated with any values, I have no idea how it would be possible to set initial value.
models.py
class Location(models.Model):
place = models.CharField(max_length=50)
postal_code = models.CharField(max_length=5)
views.py
class LocationAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
qs = Location.objects.all()
if self.q:
qs = qs.filter(place__istartswith=self.q) | qs.filter(postal_code__istartswith=self.q)
return qs
forms.py
class LocationForm(forms.ModelForm):
class Meta:
model = Location
fields = ('place',)
widgets = {
'place': autocomplete.Select2(url='location_autocomplete')
}
Any suggestions?
Thanks!
In your views.py if you pass place value like this:
LocationForm(initial={'place': place })
It will be pre-populated in your form.
Docs: https://docs.djangoproject.com/en/1.11/ref/forms/fields/#initial
Related
I need to display only one inline in a new page based on the condition like if the url has the parameter country_id then i need to display only one inline. Since I cannot make it in one ModelAdmin since the model admin has form validations in it, I used two modelAdmins for the same model. I have a readonly field called get_countries in CorporateConfig(admin which has form validations) and it will display a list of countries. If i click on a country based on that country id i need to display CorporateBrand which is an inline model to CorporateConfig on a new page(remember only that inline needs to be displayed).
class CorporateConfigurationAdmin(admin.ModelAdmin):
form = CorporateConfigurationAdminForm
inlines = [CorporateIncludeBrandAdmin, CorporateExcludeBrandAdmin,
CorporateBrandsAdmin]
def get_urls(self):
from django.urls import path
urls = super(CorporateConfigurationAdmin,self).get_urls()
filter_url = [
path('filter_brand/',self.admin_site.admin_view(self.brand_filter), name='brand-filter'),
]
return filter_url + urls
def brand_filter(self, request, obj=None):
pass
def get_countries(self, instance):
country_list = '<ul style="font-weight: bold;list-style-type:circle;">'
countries = Country.objects.all()
print("countries", countries)
for country in countries:
url = reverse_lazy('admin:brand-filter')
print("urls is",url)
country_list += '<li class="changelist">{country}</li>'.format(url=url, country=country.name,id=country.id)
country_list+='</ul>'
return mark_safe(country_list)
get_countries.short_description = 'Country Url'
Clicking on the link above should go to the custom url which is created by get_urls()
class BrandOrderFilter(CorporateConfiguration):
class Meta:
proxy = True
class CorporateInlineBaseAdmin(admin.TabularInline):
def get_queryset(self, request):
country = request.GET.get('country_id')
qs = super(CorporateInlineBaseAdmin, self).get_queryset(request)
if country:
qs = qs.filter(brand__country=country)
return qs
class CorporateBrandOrderFilterAdmin(CorporateInlineBaseAdmin):
fields = ('brand', 'order_number',)
raw_id_fields = ('brand',)
ordering = ('order_number',)
model = CorporateBrand
class BrandOrderFilteringAdmin(admin.ModelAdmin):
inlines = [CorporateBrandOrderFilterAdmin,]
Now i can able to filter according to the countries but the fields from the model and validation is done by the form. So I created another admin and without using form i tried to display only that particular inline. unfortunately i am unable to do it. After 11 hours of hardwork im posting it here so that I can get some answers. Any help is greatly appreciated!!
I achieved this functionality by creating a new form and excluding the fields I needed and in the url I fetched the parameter request.GET.get('country_id'). If there was a country_id I am returning the new form with my new inline in a new page else return the form with all the inlines.
I am using only the django admin , and trying to creating a custom filter, where is to filter the date of another model.
My models
class Avaria(models.Model):
.....
class Pavimentacao(models.Model):
avaria = models.ForeignKey(Avaria, related_name='AvariaObjects',on_delete=models.CASCADE)
date= models.DateField(blank=True,null=True)
AvariaAdmin
class AvariaAdmin(admin.ModelAdmin):
list_filter = ('')
For example, Let's say you have a model and you have to add custom ContentTypeFilter to your model admin then. you can define a class which inherit SimpleListFilter and define lookups and queryset based on your requirement and add this class to list_filter like
list_filter = [ContentTypeFilter]
Refer to docs
Example class definition is like below:
class ContentTypeFilter(admin.SimpleListFilter):
# Human-readable title which will be displayed in the
# right admin sidebar just above the filter options.
title = _('content type')
# Parameter for the filter that will be used in the URL query.
parameter_name = 'type'
def lookups(self, request, model_admin):
"""
Returns a list of tuples. The first element in each
tuple is the coded value for the option that will
appear in the URL query. The second element is the
human-readable name for the option that will appear
in the right sidebar.
"""
models_meta = [
(app.model._meta, app.model.__name__) for app in get_config()
]
return (item for item in models_meta)
def queryset(self, request, queryset):
"""
Returns the filtered queryset based on the value
provided in the query string and retrievable via
`self.value()`.
"""
if not self.value():
return
model = apps.get_model(self.value())
if model:
return queryset.models(model)
You have to add the field you want to filter. In your example if you want to filter on date you put list_filter('date'). Dont forget to register the model admin as seen here
I'm trying to include Django-autocomplete-light in my project. Everything works as expected including the creation of new choice by autocomplete. The only problem is my model contains more than one field and I'm using autocomplete only on the 'name' field. When I save my new record django-admin creates new object with the same name in the database instead of updating the record already created by autocomplete. At the end I have two records, one half empty created by autocomplete, and one valid created by django-admin.
models.py
class Montinent(models.Model):
name = models.CharField(max_length=250)
code = models.CharField(max_length=2, unique=True, db_index=True)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('-name',)
def __str__(self):
return self.name
views.py
class MontinentAutocomplete(autocomplete.Select2QuerySetView):
def get_queryset(self):
#Don't forget to filter out results depending on the visitor !
if not self.request.user.is_authenticated():
return Montinent.objects.none()
qs = Montinent.objects.all()
if self.q:
qs = qs.filter(name__istartswith=self.q)
return qs
urls.py
urlpatterns = [
url(r'^admin/', include(admin.site.urls)),
url(r'^montinent-autocomplete/$', MontinentAutocomplete.as_view(create_field='name'), name='montinent-autocomplete',),
]
admin.py
class MontinentForm(forms.ModelForm):
name = forms.ModelChoiceField(
queryset = Montinent.objects.all(),
widget = autocomplete.ModelSelect2(url='montinent-autocomplete')
)
class Meta:
model = Montinent
fields = ['name', 'slug', 'code']
class MontinentAdmin(admin.ModelAdmin):
form = MontinentForm
admin.site.register(Montinent, MontinentAdmin)
The way autocomplete creates new choice is as follow:
When the user selects that option, the autocomplete script will make a
POST request to the view. It should create the object and return the
pk, so the item will then be added just as if it already had a PK.
In this case it looks like I need to override the default django-admin save method. I tried just to make the 'name' field unique but in this case Django says this name already exist.
My question is how to override the default save method in my models.py so django to use the returned from autocomplete pk and append the missing information to the row instead of creating new object?
I was also struggling to fill more than one field with the autocomplete create choice. I needed to include the user who is creating the new entry.
The method that saves the new entry into the database is the create_object(text) method from autocomplete.Select2QuerySetView. You can read more about this method in the documentation http://django-autocomplete-light.readthedocs.io/en/master/api.html
So, to include a user I just override the method as follow:
def create_object(self, text):
return self.get_queryset().create(**{self.create_field: text, 'user' : self.request.user})
Now you don't need to have partially filled forms using the autocomplete create options. Just fill it with any field you want to.
I have a model:
class XCall(models.Model):
created_on = models.DateTimeField(auto_now_add=True)
send_on = models.DateTimeField(default=datetime.now)
recipient = models.ForeignKey(User)
text = models.CharField(max_length=4096)
backup_calls = models.IntegerField(blank=True, null=True)
And a serializer for that model:
class CallSerializer(serializers.HyperlinkedModelSerializer):
url = serializers.HyperlinkedIdentityField(
view_name='call-detail',
)
# some validation and custom field definitions
...
class Meta:
model = XCall
fields = ('url', 'id', 'text', 'recipient', 'send_on', 'backup_calls', 'status')
lookup_field= 'pk'
And here's the list view:
class CallList(generics.ListCreateAPIView):
serializer_class = CallSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrSuperuser,)
def pre_save(self, obj):
auth_user = self.request.user
obj.auth_user = auth_user
def get_queryset(self):
"""
This view should return a list of all the calls
for the currently authenticated user.
"""
auth = self.request.user
if isinstance(auth, AnonymousUser):
return []
elif auth.is_superuser:
return XCall.objects.all()
else:
return XCall.objects.filter(auth_user=auth)
In CallList's browseable API, I see the following in the POST form at the bottom:
My question is: why is there no default value set for send_on, and there is one for backup_calls? I assumed that the form would follow the XCall model specification and use datetime.now() for defaulting the former, and leave backup_calls blank (since it's nullable). How can I get the form to follow the model specifications?
You actually want to set an initial value, not a default value. See the docs. Your code should be:
from django.utils import timezone
class CallSerializer(serializers.HyperlinkedModelSerializer):
send_on = serializers.DateTimeField(initial=timezone.now())
...
A default value is the value provided to an attribute if no value is set for the field. The distinction between initial and default arguments mirrors the difference between Django's initial argument for form fields and Django's default argument for model fields.
There is a distinction between model defaults and initial values in forms. This is especially the case for default values which are actually functions because they are only called when the instance is saved. For example, which now do you want - this time at which the blank form is displayed, or the time at which the user presses "POST"? Django applies the default when saving the model if the field value is missing. To achieve what you want you need to manually set the default in the serialize field, for example:
class CallSerializer(serializers.HyperlinkedModelSerializer):
send_on = serializers.DateTimeField(default=datetime.now)
...
I have a Django model with a ManyToManyField.
1) When adding a new instance of this model via admin view, I would like to not see the M2M field at all.
2) When editing an existing instance I would like to be able to select multiple options for the M2M field, but display only a subset of the M2M options, depending on another field in the model. Because of the dependence on another field's actual value, I can't just use formfield_for_manytomany
I can do both of the things using a custom ModelForm, but I can't reliably tell whether that form is being used to edit an existing model instance, or if it's being used to create a new instance. Even MyModel.objects.filter(pk=self.instance.pk).exists() in the custom ModelForm doesn't cut it. How can I accomplish this, or just tell whether the form is being displayed in an "add" or an "edit" context?
EDIT: my relevant code is as follows:
models.py
class LimitedClassForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(LimitedClassForm, self).__init__(*args, **kwargs)
if not self.instance._adding:
# Edit form
clas = self.instance
sheets_in_course = Sheet.objects.filter(course__pk=clas.course.pk)
self.Meta.exclude = ['course']
widget = self.fields['active_sheets'].widget
sheet_choices = []
for sheet in sheets_in_course:
sheet_choices.append((sheet.id, sheet.name))
widget.choices = sheet_choices
else:
# Add form
self.Meta.exclude = ['active_sheets']
class Meta:
exclude = []
admin.py
class ClassAdmin(admin.ModelAdmin):
formfield_overrides = {models.ManyToManyField: {
'widget': CheckboxSelectMultiple}, }
form = LimitedClassForm
admin.site.register(Class, ClassAdmin)
models.py
class Course(models.Model):
name = models.CharField(max_length=255)
class Sheet(models.Model):
name = models.CharField(max_length=255)
course = models.ForeignKey(Course)
file = models.FileField(upload_to=getSheetLocation)
class Class(models.model):
name = models.CharField(max_length=255)
course = models.ForeignKey(Course)
active_sheets = models.ManyToManyField(Sheet)
You can see that both Sheets and Classes have course fields. You shouldn't be able to put a sheet into active_sheets if the sheet's course doesn't match the class's course.