I am trying to show the field(s) related to its foreignkey on html. Let's see I have two models as shown below:
models.py
from django.db import models
class Model_Item(models.Model):
item_name = models.CharField(max_length = 100, null = False, blank = False, unique = True)
item_unit = models.CharField(max_length = 20, null = False, blank = False) # can be kilogram, pound, ounce, etc
def __unicode__(self):
return self.item_name
class Model_Weight(models.Model):
item = models.ForeignKey(Model_Item, to_field = "item_name")
item_weight = models.FloatField(null = True, blank = True)
def __unicode__(self):
return self.item
On Model_Item model, each item can have its own unit, and there can be many items. Then we will choose the item on the second model (Model_Weight), and insert the value of the weight that is according to its unit.
How can we show the corresponding "item_unit" in html, such that when we have selected the "item_name", its unit will show/hover somewhere in the webpage which enables us to put the correct weight value?
These are the rest of the codes:
forms.py
from django import forms
from .models import Model_Weight
class Form_Weight(forms.ModelForm):
class Meta:
model = Model_Weight
fields = ["item", "item_weight"]
views.py
from .models import Model_Weight
from .forms import Form_Weight
from django.views.generic import CreateView
class View_Weight_CV(CreateView):
form_class = Form_Weight
def form_valid(self, form):
instance = form.save(commit = False)
instance.user = self.request.user
return super(View_Weight_CV, self).form_valid(form)
html
<form method = "POST" action = "" enctype = "multipart/form-data"> {% csrf_token %}
{{ form.item}}
<!-- {{ form.model_item.item_unit }} Automatically shows this field once an item has been selected -->
{{ form.item_weight}}
<input type = "submit" value = "Submit">
</form>
The quick solution is to change __unicode__ method definition of Model_Item model
def __unicode__(self):
# add item_unit with name
return self.item_name + " (" + self. item_unit + ")"
Now in your HTML template, the item dropdown will be shown like
{{ form.item }} #--> Bread (Kg)
#--> Rice (Kg)
#--> ...
If you want to show unit under item dropdown, keep above settings as it is and add below javascript code at bottom of your HTML template
$(document).on('change', '#id_item', function(){
// you can also make ajax request from here
// I am using selected item text for now
var item = $(this).find("option:selected").text();
// item = 'Bread (Kg)'
var result = item.match(/\((.*)\)/);
// matched text inside round brackets
// result[1] = Kg
$( "#id_item" ).after( "<p>"+result[1]+"</p>" );
});
Related
I've implemented a table search product with Ajax and it works well.
But now, I want to build dynamically my table taking in account the number of my warehouses can be increase.
search.js
data.forEach((item) => {
const newName = (item.nom).slice(0, 30) + "...";
tableBody.innerHTML += `
<tr>
<th>${item.sku}</th>
<td>${item.etat__etat}</td>
<td class="small">${newName}</td>
<td>${item.famille__nom}</td>
<td>${item.mageid}</td>
<td>${item.adresse}</td>
models.py (model for witch I need a set)
class SstStock(models.Model):
warehouse = models.ForeignKey(Warehouse, on_delete=models.CASCADE)
product = models.ManyToManyField(Produit)
qty = models.IntegerField()
last_update = models.DateTimeField(default=timezone.now)
views.py
def search_product2(request):
if request.method == 'POST':
search_str = json.loads(request.body).get('searchText')
products = Produit.objects.filter(sku__icontains=search_str) | Produit.objects.filter(
nom__icontains=search_str) | Produit.objects.filter(mageid__icontains=search_str)
data = products.values(
'id',
'sku',
'nom',
[...]
'sststock',
[...]
'cau_cli',
'maxsst2',
)
return JsonResponse(list(data), safe=False)
Directly in template I could do :
template
{% for produit in produits %}
{{ produit.sku}}<br>
{% for sst in produit.sststock_set.all %}
<span>{{sst.warehouse.code}} - {{ sst.qty }}</span><br>
{% endfor %}
<br>
{% endfor %}
But I couldn't find the way to pass the the sststock_set.all() in the JsonResponse. I got well a "sststock" value in it but it contains only the last value of the set instead of an array/dict of the whole set.
console.log()
qty: 7
sku: "ACP863"
sststock: 68095
68095 is the last ID of my set.
Worse, when I try to get item.sststock in the ForEach product, in my JS, it returns Undefined.
Any idea please ?
Found the way to apply #WillemVanOnsem advice with serializer.
Before all, my first error war to apply ManyToMany instead of ForeignKey on:
product = models.ManyToManyField(Produit)
After, I have set a serializer that retrieves the different stocks (warehouse_id + qty) and adds it to the Product model (with "source" parameter):
serializers.py
from rest_framework import serializers
from .models import Produit, SstStock
class StockSearchSerializer(serializers.ModelSerializer):
class Meta:
model = SstStock
fields = '__all__'
fields = ['warehouse_id', 'qty']
class ProductSearchSerializer(serializers.ModelSerializer):
sststock = StockSearchSerializer(source='sststock_set', many=True)
class Meta:
model = Produit
fields = '__all__'
To finish, I use the serializer with "many=True" in the view and return its result that will be handled by JS on my search page:
views.py
def search_product(request):
if request.method == 'POST':
search_str = json.loads(request.body).get('searchText')
products = Produit.objects.prefetch_related(
Prefetch('sststock_set',
SstStock.objects.select_related('warehouse'))
).filter(sku__icontains=search_str) | Produit.objects.filter(
nom__icontains=search_str) |
Produit.objects.filter(mageid__icontains=search_str)
serializer = ProductSearchSerializer(products, many=True)
data = serializer.data
return JsonResponse(list(data), safe=False)
And as wished, stocks array is added in the json response
I have seen this approach in many web applications (e.g. when you subscribe for an insurance), but I can't find a good way to implement it in django. I have several classes in my model which inherit from a base class, and so they have several fields in common. In the create-view I want to use that inheritance, so first ask for the common fields and then ask for the specific fields, depending on the choices of the user.
Naive example, suppose I want to fill a database of places
class Place(Model):
name = models.CharField(max_length=40)
address = models.CharField(max_length=100)
class Restaurant(Place):
cuisine = models.CharField(max_length=40)
website = models.CharField(max_length=40)
class SportField(Place):
sport = models.CharField(max_length=40)
Now I would like to have a create view when there are the common fields (name and address) and then the possibility to choose the type of place (Restaurant / SportField). Once the kind of place is selected (or the user press a "Continue" button) new fields appear (I guess to make it simple the page need to reload) and the old one are still visible, already filled.
I have seen this approach many times, so I am surprised there is no standard way, or some extensions already helping with that (I have looked at Form Wizard from django-formtools, but not really linked to inheritance), also doing more complicated stuff, as having more depth in inheritance.
models.py
class Place(models.Model):
name = models.CharField(max_length=40)
address = models.CharField(max_length=100)
class Restaurant(Place):
cuisine = models.CharField(max_length=40)
website = models.CharField(max_length=40)
class SportField(Place):
sport = models.CharField(max_length=40)
forms.py
from django.db import models
from django import forms
class CustomForm(forms.Form):
CHOICES = (('restaurant', 'Restaurant'), ('sport', 'Sport'),)
name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Name'}))
address = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Address'}))
type = forms.ChoiceField(
choices=CHOICES,
widget=forms.Select(attrs={'onChange':'renderForm();'}))
cuisine = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Cuisine'}))
website = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Website'}))
sport = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Sport'}))
views.py
from django.http.response import HttpResponse
from .models import Restaurant, SportField
from .forms import CustomForm
from django.shortcuts import render
from django.views import View
class CustomView(View):
def get(self, request,):
form = CustomForm()
return render(request, 'home.html', {'form':form})
def post(self, request,):
data = request.POST
name = data['name']
address = data['address']
type = data['type']
if(type == 'restaurant'):
website = data['website']
cuisine = data['cuisine']
Restaurant.objects.create(
name=name, address=address, website=website, cuisine=cuisine
)
else:
sport = data['sport']
SportField.objects.create(name=name, address=address, sport=sport)
return HttpResponse("Success")
templates/home.html
<html>
<head>
<script type="text/javascript">
function renderForm() {
var type =
document.getElementById("{{form.type.auto_id}}").value;
if (type == 'restaurant') {
document.getElementById("{{form.website.auto_id}}").style.display = 'block';
document.getElementById("{{form.cuisine.auto_id}}").style.display = 'block';
document.getElementById("{{form.sport.auto_id}}").style.display = 'none';
} else {
document.getElementById("{{form.website.auto_id}}").style.display = 'none';
document.getElementById("{{form.cuisine.auto_id}}").style.display = 'none';
document.getElementById("{{form.sport.auto_id}}").style.display = 'block';
}
}
</script>
</head>
<body onload="renderForm()">
<form method="post" action="/">
{% csrf_token %}
{{form.name}}<br>
{{form.address}}<br>
{{form.type}}<br>
{{form.website}}
{{form.cuisine}}
{{form.sport}}
<input type="submit">
</form>
</body>
</html>
Add templates folder in settings.py
TEMPLATES = [
{
...
'DIRS': [os.path.join(BASE_DIR, 'templates')],
...
]
I've created a 2-page working example using modified Class Based Views.
When the form is submitted on the first page, an object of place_type is created. The user is then redirected to the second page where they can update existing details and add additional information.
No separate ModelForms are needed because the CreateView and UpdateView automatically generate the forms from the relevant object's model class.
A single template named place_form.html is required. It should render the {{ form }} tag.
# models.py
from django.db import models
from django.urls import reverse
class Place(models.Model):
"""
Each tuple in TYPE_CHOICES contains a child class name
as the first element.
"""
TYPE_CHOICES = (
('Restaurant', 'Restaurant'),
('SportField', 'Sport Field'),
)
name = models.CharField(max_length=40)
address = models.CharField(max_length=100)
place_type = models.CharField(max_length=40, blank=True, choices=TYPE_CHOICES)
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('place_update', args=[self.pk])
# Child models go here...
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path('create/', views.PlaceCreateView.as_view(), name='place_create'),
path('<pk>/', views.PlaceUpdateView.as_view(), name='place_update'),
]
# views.py
from django.http import HttpResponseRedirect
from django.forms.models import construct_instance, modelform_factory
from django.views.generic.edit import CreateView, UpdateView
from django.urls import reverse_lazy
from . import models
class PlaceCreateView(CreateView):
model = models.Place
fields = '__all__'
def form_valid(self, form):
"""
If a `place_type` is selected, it is used to create an
instance of that Model and return the url.
"""
place_type = form.cleaned_data['place_type']
if place_type:
klass = getattr(models, place_type)
instance = klass()
obj = construct_instance(form, instance)
obj.save()
return HttpResponseRedirect(obj.get_absolute_url())
return super().form_valid(form)
class PlaceUpdateView(UpdateView):
fields = '__all__'
success_url = reverse_lazy('place_create')
template_name = 'place_form.html'
def get_object(self, queryset=None):
"""
If the place has a `place_type`, get that object instead.
"""
pk = self.kwargs.get(self.pk_url_kwarg)
if pk is not None:
obj = models.Place.objects.get(pk=pk)
if obj.place_type:
klass = getattr(models, obj.place_type)
obj = klass.objects.get(pk=pk)
else:
raise AttributeError(
"PlaceUpdateView must be called with an object pk in the URLconf."
)
return obj
def get_form_class(self):
"""
Remove the `place_type` field.
"""
model = self.object.__class__
return modelform_factory(model, exclude=['place_type',])
We did something similar manually, we created the views and forms based on design and did the linkage based on if conditions.
I think a nice solution would be to dynamically access subclasses of the main class and then do the necessary filtering/lists building.
UPD: I've spent some more time today on this question and made a "less raw" solution that allows to use the inheritance.
You can also check the code below deployed here. It has only one level of inheritance (as in example), though, the approach is generic enough to have multiple levels
views.py
def inheritance_view(request):
all_forms = {form.Meta.model: form for form in forms.PlaceForm.__subclasses__()}
all_forms[models.Place] = forms.PlaceForm
places = {cls._meta.verbose_name: cls for cls in models.Place.__subclasses__()}
# initiate forms with the first one
context = {
'forms': [forms.PlaceForm(request.POST)],
}
# check sub-forms selected on the forms and include their sub-forms (if any)
for f in context['forms']:
f.sub_selected = request.POST.get('{}_sub_selected'.format(f.Meta.model._meta.model_name))
if f.sub_selected:
sub_form = all_forms.get(places.get(f.sub_selected))
if sub_form not in context['forms']:
context['forms'].append(sub_form(request.POST))
# update some fields on forms to render them on the template
for f in context['forms']:
f.model_name = f.Meta.model._meta.model_name
f.sub_forms = {x.Meta.model._meta.verbose_name: x for x in f.__class__.__subclasses__()}
f.sub_options = f.sub_forms.keys() # this is for rendering selector on the form for the follow-up forms
page = loader.get_template(template)
response = HttpResponse(page.render(context, request))
return response
forms.py
class PlaceForm(forms.ModelForm):
class Meta:
model = models.Place
fields = ('name', 'address',)
class RestaurantForm(PlaceForm):
class Meta:
model = models.Restaurant
fields = ('cuisine', 'website',)
class SportFieldForm(PlaceForm):
class Meta:
model = models.SportField
fields = ('sport',)
templates/inheritance.html
<body>
{% for form in forms %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
{% if form.sub_options %}
<select class="change-place" name="{{ form.model_name }}_sub_selected">
{% for option in form.sub_options %}
<option value="{{ option }}" {% if option == form.sub_selected %}selected{% endif %}>{{ option }}</option>
{% endfor %}
</select>
{% endif %}
<button type="submit">Next</button>
</form>
{% endfor %}
</body>
What I didn't make here is saving the form to the database. But it should be rather trivial using the similar snippet:
for f in context['forms']:
if f.is_valid():
f.save()
Add a PlaceType table, and a FK, e.g. type_of_place, to the Place table:
class PlaceType(Model):
types = models.CharField(max_length=40) # sportsfield, restaurants, bodega, etc.
class Place(Model):
name = models.CharField(max_length=40)
address = models.CharField(max_length=100)
type_of_place = models.ForeignKey('PlaceType', on_delete=models.SET_NULL, null=True)
class Restaurant(Place):
cuisine = models.CharField(max_length=40)
website = models.CharField(max_length=40)
This allows you to create a new Place as either SportsField, restaurant or some other type which you can easily add in the future.
When a new place is created, you'll use the standard CreateView and Model Form. Then, you can display a second form which also uses a standard CreateView that is based on the type_of_place value. These forms can be on the same page (and with javascript on the browser side, you'll hide the second form until the first one is saved) or on separate pages--which may be more practical if you intend to have lots of extra columns. The two key points are as follows:
type_of_place determines which form, view, and model to use. For
example, if user chooses a "Sports Field" for type_of_place, then
you know to route the user off to the SportsField model form;
CreateViews are designed for creating just one object/model. When
used as intended, they are simple and easy to maintain.
There are lot of way you can handle multiple froms in django. The easiest way to use inlineformset_factory.
in your froms.py:
forms .models import your model
class ParentFrom(froms.From):
# add fields from your parent model
Restaurant = inlineformset_factory(your parent model name,Your Child model name,fields=('cuisine',# add fields from your child model),extra=1,can_delete=False,)
SportField = inlineformset_factory(your parent model name,Your Child model name,fields=('sport',# add fields from your child model),extra=1,can_delete=False,)
in your views.py
if ParentFrom.is_valid():
ParentFrom = ParentFrom.save(commit=False)
Restaurant = Restaurant(request.POST, request.FILES,) #if you want to add images or files then use request.FILES.
SportField = SportField(request.POST)
if Restaurant.is_valid() and SportField.is_valid():
ParentFrom.save()
Restaurant.save()
SportField.save()
return HttpResponseRedirect(#your redirect url)
#html
<form method="POST" enctype="multipart/form-data">
{% csrf_token %}
#{{ Restaurant.errors}} #if you want to show error
{{ Restaurant}}
{{ SportField}}
{{form}}
</form>
you can use simple JavaScript in your html for hide and show your any froms fields
I am new to Django and trying to save some data from the form to the model. I want to insert into two models which have a foreign key constraint relationship (namely Idea and IdeaUpvotes) i.e. from a html template to a view.
My submit code is:
def submitNewIdea(request):
#get the context from the request
context = RequestContext(request)
print(context)
#A HTTP POST?
if request.method == 'POST':
form = submitNewIdeaForm(request.POST)
# Have we been provided with a valid form?
if form.is_valid():
# Save the new Idea to the Idea model
print(request.POST.get("IdeaCategory"))
print(request.POST.get("IdeaSubCategory"))
i = Idea( idea_heading = form["idea_heading"].value()
,idea_description = form["idea_description"].value()
,idea_created_by = form["idea_created_by"].value()
,idea_votes = form["idea_votes"].value()
,idea_category = request.POST.get("IdeaCategory") #value from dropdown
,idea_sub_category = request.POST.get("IdeaSubCategory") #value from dropdown
)
i.save()
# get the just saved id
print(Idea.objects.get(pk = i.id))
iu = IdeaUpvotes(idea_id = Idea.objects.get(pk = i.id)
,upvoted_by = form["upvoted_by"].value()
,upvoted_date = timezone.now() )
iu.save()
form.save(commit = True)
# Now call the index() view.
# The user will be shown the homepage.
return index(request)
else:
# The supplied form contained errors - just print them to the terminal.
print (form.errors)
else:
# If the request was not a POST, display the form to enter details.
form = submitNewIdeaForm()
# Bad form (or form details), no form supplied...
# Render the form with error messages (if any).
return render(request,'Ideas/Index.html',{'form' :form})
form.py --->
class submitNewIdeaForm(forms.ModelForm):
idea_heading = forms.CharField(label = "idea_heading",max_length =1000,help_text= "Please enter the idea heading.")
idea_description= forms.CharField(label = "idea_description",max_length =1000,help_text= "Please enter the idea description.",widget=forms.Textarea)
idea_created_by=forms.CharField(max_length =200, widget = forms.HiddenInput(), initial='wattamw')
idea_votes = forms.IntegerField(widget=forms.HiddenInput(), initial=1)
upvoted_by=forms.CharField(max_length =200, widget = forms.HiddenInput(), initial='abcde')
"""
#commented code
#idea_category_name = forms.CharField(label = "idea_category_name",max_length =250,help_text= "Please select an Idea Category.")
#idea_sub_category = forms.CharField(label = "idea_sub_category",max_length =250,help_text= "Please select an Idea Sub Category.")
idea_category_name = forms.ModelChoiceField(
queryset = IdeaCategory.objects.all(),
widget=autocomplete.ModelSelect2(url='category-autocomplete'))
idea_sub_category = forms.ModelChoiceField(
queryset = IdeaSubCategory.objects.all(),
widget = autocomplete.ModelSelect2(
url='subcategory-autocomplete',
forward = (forward.Field('idea_category_name','id'),)))
"""
class Meta:
model = Idea
fields = ('idea_heading','idea_description','idea_created_by','idea_votes','idea_category_name','idea_sub_category')
class Meta:
model = IdeaUpvotes
fields = ('upvoted_by',)
def __init__(self,*args,**kwargs):
super(submitNewIdeaForm,self).__init__(*args,**kwargs)
self.fields['idea_category_name'] = forms.ModelChoiceField(
queryset = IdeaCategory.objects.all(),
widget=autocomplete.ModelSelect2(url='category-autocomplete'))
self.fields['idea_sub_category'] = forms.ModelChoiceField(
queryset = IdeaSubCategory.objects.all(),
widget = autocomplete.ModelSelect2(
url='subcategory-autocomplete',
forward = (forward.Field('idea_category_name','id'),)))
I am able to print the values and see that they are passed,but I still get the following error :
Error Description
I have removed any foreign key references to the table, the fields are simple character fields.
Please help me out.
Thanks.
In the first place, your form validation is failing. It seems to me that your form template may be wrong.
The second thing is that you don't use Django forms properly. All you need to do to achieve the functionality you are looking for is to use ModelForm and let the form's save method to create the object for you. All you need to do is:
Associate your SubmitNewIdeaForm with the Idea model:
# forms.py
class SubmitNewIdeaForm(ModelForm):
class Meta:
model = Idea
fields = (
'idea_heading',
'idea_description',
'idea_created_by',
'idea_votes',
'idea_category',
'idea_sub_category'
)
Render the form
#form_template.html
<form action="{% url 'your_url' %}" method="post">
{% csrf_token %}
{{ form }}
<input type="submit" value="Submit" />
</form>
Finally jsut check if the form is valid and call form.save() like so:
def submitNewIdea(request):
if form.is_valid():
form.save()
That's it! I hope that I helped you.
Cheers!
Finished = models.IntegerField('Finished percentage', error_messages={'required':''})
Worked for me.
The current admin widget for ArrayField is one field, with comma as delimiter, like this (text list):
This isn't ideal because I would have longer texts (even 20 words) and contain commas. I could change the delimiter to be something else but that still doesn't help with unreadable content in admin.
What I would like is having a list of fields, that I can alter in admin. Something similar to the following image
I could use another table to solve this, but I wonder if it's possible to solve it this way.
Unfortunately Django does not ship with a convenient widget for ArrayFields yet. I'd suggest you to create your own. Here is an example for Django>=1.11:
class DynamicArrayWidget(forms.TextInput):
template_name = 'myapp/forms/widgets/dynamic_array.html'
def get_context(self, name, value, attrs):
value = value or ['']
context = super().get_context(name, value, attrs)
final_attrs = context['widget']['attrs']
id_ = context['widget']['attrs'].get('id')
subwidgets = []
for index, item in enumerate(context['widget']['value']):
widget_attrs = final_attrs.copy()
if id_:
widget_attrs['id'] = '%s_%s' % (id_, index)
widget = forms.TextInput()
widget.is_required = self.is_required
subwidgets.append(widget.get_context(name, item, widget_attrs)['widget'])
context['widget']['subwidgets'] = subwidgets
return context
def value_from_datadict(self, data, files, name):
try:
getter = data.getlist
except AttributeError:
getter = data.get
return getter(name)
def format_value(self, value):
return value or []
Here is the widget template:
{% spaceless %}
<div class="dynamic-array-widget">
<ul>
{% for widget in widget.subwidgets %}
<li class="array-item">{% include widget.template_name %}</li>
{% endfor %}
</ul>
<div><button type="button" class="add-array-item">Add another</button></div>
</div>
{% endspaceless %}
A few javascript (using jQuery for convenience):
$('.dynamic-array-widget').each(function() {
$(this).find('.add-array-item').click((function($last) {
return function() {
var $new = $last.clone()
var id_parts = $new.find('input').attr('id').split('_');
var id = id_parts.slice(0, -1).join('_') + '_' + String(parseInt(id_parts.slice(-1)[0]) + 1)
$new.find('input').attr('id', id);
$new.find('input').prop('value', '');
$new.insertAfter($last);
};
})($(this).find('.array-item').last()));
});
And you would also have to create your own form field:
from itertools import chain
from django import forms
from django.contrib.postgres.utils import prefix_validation_error
class DynamicArrayField(forms.Field):
default_error_messages = {
'item_invalid': 'Item %(nth)s in the array did not validate: ',
}
def __init__(self, base_field, **kwargs):
self.base_field = base_field
self.max_length = kwargs.pop('max_length', None)
kwargs.setdefault('widget', DynamicArrayWidget)
super().__init__(**kwargs)
def clean(self, value):
cleaned_data = []
errors = []
value = filter(None, value)
for index, item in enumerate(value):
try:
cleaned_data.append(self.base_field.clean(item))
except forms.ValidationError as error:
errors.append(prefix_validation_error(
error, self.error_messages['item_invalid'],
code='item_invalid', params={'nth': index},
))
if errors:
raise forms.ValidationError(list(chain.from_iterable(errors)))
if cleaned_data and self.required:
raise forms.ValidationError(self.error_messages['required'])
return cleaned_data
Finally, set it explicitly on your forms:
class MyModelForm(forms.ModelForm):
class Meta:
model = MyModel
fields = ['foo', 'bar', 'the_array_field']
field_classes = {
'the_array_field': DynamicArrayField,
}
Try to take a look in this one :
Better ArrayField admin widget?
I think is more about a js thing after you have rendered the Array in a different way.
I have that situation:
I have models Item, Region and Country.
class Item(models.Model):
name = models.CharField(max_length=255)
alias = models.SlugField(unique=True)
country = models.OneToOneField(Country, default=0)
region = models.OneToOneField(Region, default=0, related_name='')
class Region(models.Model):
name = models.CharField(max_length=100)
country = models.ForeignKey(Country, default=0)
class Country(models.Model):
name = models.CharField(max_length=100)
When I add an item in the admin area and I select country I want to automatically build the region select with Regions only from the selected Country.
I know how to do it in javascript, but i don't know how CORRECT it is do that in Django.
You need to override the clean method of your admin form:
def clean(self):
super(CustomItemForm, self).clean() #if necessary
if 'region' in self._errors:
"""
reset the value (something like this i
think to set the value b/c it doesnt get set
b/c the field fails validation initially)
"""
region = Region.objects.get(pk=self.data['region'])
self.initial['region'] = region.id
self.cleaned_data['region'] = region
self.region = region
# remove the error
del self._errors['region']
return self.cleaned_data
If you're having trouble having django accept the value you selected, because it doesn't think it should have been there in the first place, have the queryset refer to every possible value, but then override the "choices" attribute with an empty set. The latter part will avoid django construct a huge option list that is just going to be overridden by the dynamic javascript.
I know this isn't drop-in-able for the admin screens, but I found this page while looking to do a similar thing for non-admin, so I thought I'd answer it here, too. Here's what I'm using:
in views.py:
from django.shortcuts import render_to_response
from django import forms
from django.template import RequestContext
from django.http import HttpResponseRedirect, HttpResponse
from django.core.urlresolvers import reverse
from django.core import serializers
# other imports
class AddRouteForm ( forms.Form ):
router = forms.ModelChoiceField ( queryset=Router.objects.all() )
peer = forms.ModelChoiceField ( queryset=Peer.objects.all() )
peer.choices = []
# other stuff not relevant
def get_peers ( request ):
router_id = request.GET['router_id']
router = Router.objects.get(id=router_id)
data = serializers.serialize ( "json", router.peer_set.filter(transit_capable=True) )
return HttpResponse ( data, content_type="text/json" )
def add_route ( request ):
if request.method == "POST":
form = AddRouteForm ( request.POST )
if form.is_valid ():
# TODO something here
return HttpResponseRedirect ( reverse ( "staticroute.views.index" ) )
else:
form = AddRouteForm ()
return render_to_response ( "staticroute/add_route.html", locals(), RequestContext(request) )
In the html file (jQuery needs to be loaded too):
<form method="POST">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Add Route" />
</form>
<script>
$("#id_router").change ( function () {
var router_id = $('#id_router').val();
var items = [];
items.push ( '<option value="" selected="selected">---------</option>' );
$("#id_peer").html ( items.join('') );
if ( router_id != "" ) {
$.getJSON ( '{% url staticroute.views.get_peers %}', {router_id:router_id}, function(data) {
$.each ( data, function ( index, val ) {
items.push ( '<option value="' + val.pk + '">' + val.fields.description + ' (' + val.fields.address + ')</option>');
} );
$("#id_peer").html ( items.join('') );
} );
}
} ).change();
</script>
I solved that problem not with proposed libraries (with simple javascript), but exist another problem: when i changed country - select region changes automatically, but i can't save new region value (when another region from the same country - not problem), because error Select a valid choice. That choice is not one of the available choices.
Models are the same in first question, but default value for model country equal 1.
My first way was change country select through formfield_key function,
def formfield_for_foreignkey(self, db_field, request, **kwargs):
if db_field.name == 'region':
kwargs["queryset"] = Region.objects.filter(country=self.country)
return db_field.formfield(**kwargs)
but i don't know is the saving objects, or editing objects.
Here wrote - the best way is changing through form
and now i have code:
class Item(models.Model):
country = models.ForeignKey(Country, default=1)
region = models.ForeignKey(Region, related_name='')
class ItemAdmin(admin.ModelAdmin):
form = CustomItemForm
prepopulated_fields = {"alias": ("name",)}
list_filter = ('country', 'category',)
class CustomItemForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
try:
country = kwargs['instance'].country
except KeyError:
country = 1
super(CustomItemForm, self).__init__(*args, **kwargs)
self.fields['region'].queryset = Region.objects.filter(country=country)