I have two models:
class Entity(models.Model):
entity = models.CharField(primary_key=True, max_length=12)
entityDescription = models.CharField(max_length=200)
def __str__(self):
return self.entityDescription
class Action(models.Model):
entity = models.ForeignKey(Entity, on_delete=models.CASCADE, db_column='entity')
entityDescription = models.CharField(max_length=200)
action = models.CharField(max_length=50)
def __str__(self):
return '%s' % self.entity
I have a model form and model formset, along with a form helper for crispy-forms:
class ActionForm(ModelForm):
class Meta:
model = Action
fields = '__all__'
def __init__(self, *args, **kwargs):
super(AlertForm, self).__init__(*args, **kwargs)
instance = getattr(self, 'instance', None)
if instance and instance.pk:
disabledFields = ['entity',
'entityDescription']
for field in disabledFields:
self.fields[field].disabled=True
else:
self.fields['entity'].blank=True
self.fields['entityDescription'] = ModelChoiceField(queryset=Entity.objects.all())
ActionFormSet = modelformset_factory(Action, extra=1, exclude=(), form=ActionForm)
class ActionFormsetHelper(FormHelper):
def __init__(self, *args, **kwargs):
super(ActionFormsetHelper, self).__init__(*args, **kwargs)
self.form_method = 'post'
self.template = 'bootstrap/table_inline_formset.html'
self.add_input(Submit("submit", "Submit"))
self.layout = Layout(
Field('entity', css_class="input", type="hidden"),
Field('entityDescription', css_class="input"),
Field('action', css_class="input")
)
I have a view:
def actions(request):
newActions = Action.objects.filter(action='')
formset = ActionFormSet(request.POST or None, queryset=newActions)
helper = ActionFormsetHelper()
context = {'formset':formset, 'helper':helper}
if request.method == 'POST':
for form in formset:
if form.is_valid():
if form.has_changed():
obj = form.save(commit=False)
obj.entity = form.cleaned_data['entityDescription']
obj.save()
return HttpResponseRedirect('/actions')
return render(request, 'actions/actions.html', context)
So my rendered page looks something like this:
entityDescription action
Jim Halpert [Blank Cell]
Michael Scott [Blank Cell]
[Blank Cell] [Blank Cell]
entity is hidden, and entityDescription is driven by the Entity model. When the user selects an entityDescription, I would like entity to be autopopulated in the Action model. Logically, this means entityDescription would need to go back to the Entity model, find the corresponding entity primary key, and place that value in the entity foreign key in the Action model.
My attempt at this is in the view. I saved the form without committing, tried to assign some value to entity, then attempted to commit the form. This attempt results in this error:
Cannot assign "<Some Entity Description>": "Action.entity" must be a "Entity" instance.
This makes sense, because I tried to just assign the entityDescription to entity instead of assigning the entity. I next tried to just get the entity in a hacky manner since it is the first word in entityDescription:
obj.entity = form.cleaned_data['entityDescription'].split(' ', 1)[0]
This resulted in the same error, despite entity looking correct in the error. These errors are occurring for both the existing model formset members AND the new member.
How do I retrieve the primary key of the Entity model when the user selects a value from the Entity model? Then how do I assign that primary key to the corresponding foreign key field in the Action model?
Edit:
So Jim and Michael are existing records in Action. The user can assign them an action. The blank line is a new action. The user can choose the entityDescription from the Entity model. entity is a hidden field (i.e. 1 for Jim, 2 for Michael).
When the user selects an entityDescription for the new line (i.e. user selects Jim), the primary key (1) should be entered into the hidden entity field prior to saving the forms.
Another Edit:
After further investigation, if I implement the solution in the provided answer, the problem is here:
obj.entity = Entity.objects.get(pk=pk)
This is actually returning the entityDescription of the Entity model (i.e. what is defined by def __str__) rather than the primary key. I attempted to change this to...
obj.entity = Entity.objects.get(pk=pk).entity
...but this results in the primary key being returned as a string rather than an object. Therefore it can't be saved to the database. How would I go about turning this string into an object? In other words, how do I use the query language to get one value from one field from one object of a Django model?
Your obj.entity should assign as object, and form.cleaned_data isn't return as object.
try to print what the output of this, If I following your case, makesure it as id/pk from the selected entity:
print(form.cleaned_data['entityDescription'])
# eg: 16
Then:
obj.entity = form.cleaned_data['entityDescription']
should be;
pk = form.cleaned_data['entityDescription']
obj.entity = get_object_or_404(Entity, pk=pk)
# ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ this is object instance
# OR
pk = form.cleaned_data['entityDescription']
obj.entity = Entity.objects.get(pk=pk)
# ^^^^^^^^^^^^^^^^^^^^^^^^^ this is object instance
Related
I am using Django 1.97 and have the following models:
class Symbol(models.Model):
symbol = models.CharField(max_length=15)
# more fields
class Position(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL)
symbol = models.ForeignKey(Symbol)
# more fields
def get_user_positions_qs(self, user):
positions = Position.objects.all().select_related('symbol').filter(user=user).order_by('symbol')
return positions
I need to display a modelform in a template for the logged in user's positions, but the symbol field needs to be disabled. So far I have the following in my view:
position = Position()
form_class = PortfolioForm
PositionModelFormSet = modelformset_factory(Position, fields=('symbol', 'various_other_fields'), form=form_class)
def get(self, request):
positions = self.position.get_user_positions_qs(user=request.user)
position_formset = self.PositionModelFormSet(queryset=positions)
return render(request,
'template.html',
{'position_formset': position_formset})
And the form:
class PortfolioForm(forms.ModelForm):
def __init__(self, *args, **kwargs):
super(PortfolioForm, self).__init__(*args, **kwargs)
self.fields['symbol'].widget = forms.TextInput(attrs={'disabled': True})
class Meta:
model = Position
fields = ['symbol', 'various other fields']
The problem is that when displaying the form, the symbol field only contains the foreign key id instead of the actual symbol CharField from the symbol model. If I change the form so that the symbol field is not disabled, then the symbol field in the template displays the correct value, however it then has a dropdown allowing the user to change the symbol which is not allowed.
So my question is, how do I disable the symbol field in the template while continuing to display the symbol value found in the CharField from the symbol model (ie: not just the foreign key id pointing to that record). It appears that if you have a form that includes a field from another model, then trying to make that field disabled results in the field only displaying a foreign key instead of the actual value from the other table.
The problem is that you are using a Textinput widget for a FKey field, which is treated as a lookup field by default. Why not just have it be a disabled dropdown field?
self.fields['symbol'].widget.attrs['disabled'] = 'disabled'
Edit: Alternative solution
Based on your comment, here's another solution that I think should work:
class PortfolioForm(forms.ModelForm):
symbol_text = forms.CharField()
class Meta:
fields = ('symbol', 'other fields')
model = Position
def __init__(self, *args, **kwargs):
super(PortfolioForm, self).__init__(*args, **kwargs)
self.fields['symbol_text'].widget.attrs['value'] = self.instance.symbol
self.fields['symbol_text'].widget.attrs['disabled'] = 'disabled'
I keep running into this error:
Cannot assign "u'Foo Group'": "Team.membership_group" must be a "Group" instance.
In my Django application, I give the user an option to create a Team. The team has a memebership_group ForeignKey attribute which maps to 'Group' (django.contrib.auth.models.Group). In the form, I've changed the widget to be a CharField so that if a group that the user types isn't actually a group, my code should create it. Here is my form:
class TeamForm(ModelForm):
"""Form to create and modify systems"""
membership_group = CharField()
manager = ModelChoiceField(queryset=Manager.objects.all(), required=True)
class Meta:
model = Team
fields = ['name', 'manager', 'membership_group']
In my views (or possibly I need to write the code elsewhere?), I want to take the string value and run a get_or_create to either return the existing group or create a new one. Here is the code in my views that isn't working:
class TeamCreateView(AutoEventLogMixin, SuccessMessageMixin, PermissionRequiredMixin, CreateView):
"""View to create Teams"""
form_class = TeamForm
model = Team
permission_required = 'teams.add_team'
success_message = "Team '%(name)s' created successfully."
template_name = 'teams/team_form.html'
def form_valid(self, form):
team_created = super(TeamCreateView, self).form_valid(form)
team = self.object
group_name = form.instance.membership_group
group_name.encode('utf-8')
membership_group = Group.objects.get_or_create(name=group_name)[0]
team.membership_group = membership_group
team.save()
return team_created
What am I doing wrong? What code do I need to add (and where) to be able to serialize/deserialize the membership group value from string to group and vice versa
I think in anyway it is not a good idea to do type translation in form_valid. If you use django1.9, then consider to create a customized FormField:
from django import forms
class MyGroupField(forms.Field):
def to_python(self, group_name):
return Group.objects.get_or_create(name=group_name)[0]
class TeamForm(ModelForm):
membership_group = MyGroupField()
I am building a filter for my website where people can filter by cuisine. In order to achieve this I used a model form to receive input information that sets the filter variable in a query in my view. However as you can see in the image linked below, the default select for my cuisine categories is '-------' .
How would I go about changing this to say the words 'all' and setting a value so my filter queries everything for those categories? I think it has something to do with using a form method but I have been unable to understand what is actually happening in some of the examples.
Here is my simple code
Models
class Cuisine(models.Model):
name = models.CharField()
def __str__(self):
return self.name
class Food(models.Model):
name = models.CharField()
cuisine = models.ForeignKey(Cuisine)
def __str__(self):
return self.name
Views
def home_page(request):
if request.method == 'GET':
form = FilterForm(request.GET)
if form.is_valid():
cuisine = form.cleaned_data['cuisine']
food = get_list_or_404(Food, cuisine__pk=cuisine.pk)
return render('base.html', {'food': food, 'form':form})
else:
form = FilterForm()
return render('base.html', {'form':form})
Form
class FilterForm(forms.ModelForm):
class Meta:
model = Cuisine
fields = ('name')
I wouldn't use a modelform here. You only have one field, and you're not using it to create or edit instances of Food or Cuisine. It would be simpler to use a manual form with a ModelChoiceField, to which you can pass the empty_label parameter.
class FilterForm(forms.Form):
cuisine = forms.ModelChoiceField(queryset=Cuisine.objects.all(),
empty_label="All")
(Note you could do this with the ModelForm as well, but that just makes it even more pointless, as you are now not using any of the ModelForm functionality.)
During form processing I'd like to be able to set a foreign key field on a model object without the user having to select the key from a dropdown.
For instance:
#models.py
class AAA(models.Model):
some_field = models.TextField()
class BBB(models.Model):
another_field = models.TextField()
key_field = models.ForeignKey('AAA')
The user will navigate from a view showing an instance of 'AAA' to a create_object style view that will create an instance of 'BBB' given a parameter referring to 'AAA'. The foreign key is set in code to point back to the 'AAA' instance.
The django comments framework seems to do this but I can't figure out how.
Any ideas? I'm sure it should be quite simple.
You can exclude the key_field from your model form, save with commit=False, then set key_field in your view before saving to the database.
class BBBForm(forms.ModelForm):
class Meta:
model = BBB
exclude = ("key_field",)
def create_view(request, **kwargs):
if request.method == "POST":
aaa = # get aaa from url, session or somewhere else
form = BBBForm(request.POST)
if form.is_valid():
bbb = form.save(commit=False)
bbb.key_field = aaa
bbb.save()
return HttpResponseRedirect("/success-url/")
...
As the user creates a BBB via an instance of AAA, this should be reflected in the URL, i.e., your "create_object style view" will get a parameter identifying an AAA object. You can use it to get the object from the database and create your BBB object accordingly:
from django.shortcuts import get_object_or_404
def create_bbb_view(request, aaa_id):
a = get_object_or_404(AAA, id=aaa_id)
form = MyBBBCreationForm(request.POST) # or similar code
if form.is_valid():
b = BBB.objects.create(key_field=a) # plus other data from form
# ...
(You could also set key_field to aaa_id directly, but it's probably a good idea to check if the object exists.)
I have a model that looks like this and stores data as key-value pairs.
class Setting(models.Model):
company = models.ForeignKey(
Company
)
name = models.CharField(
null=False, max_length=255
)
value= models.CharField(
null=False, max_length=255
)
I have a custom Manager on this Model which overrides the get method. When the queries my Model like Settings.objects.get(company=1), I use my over-ridden get method to execute a self.objects.filter(company=1) which returns a list of objects. Can I generate one single custom QuerySet which has all the key-value pairs as fields.
Example:
If the data in my Model was like this:
company name value
------- ---- -----
1 theme custom
1 mode fast
1 color green
I'd like to return a query set that would be pivoted like so when someone executed Settings.objects.get(company=1):
company theme mode color
------ ----- ---- -----
1 custom fast green
I've tried to be verbose but do let me know if I should explain better. I'm not sure if the Django Models allow this scenario.
Thank you everyone.
Edit: Using Proxy models
Is this something I could accomplish using Proxy Models i.e. having a base model to store the key value fields and custom proxy model with normal get and save method?
Here's how I did it.
I needed to do this because I had a Model that stored information as key value pairs and I needed to build a ModelForm on that Model but the ModelForm should display the key-value pairs as fields i.e. pivot the rows to columns. By default, the get() method of the Model always returns a Model instance of itself and I needed to use a custom Model. Here's what my key-value pair model looked like:
class Setting(models.Model):
domain = models.ForeignKey(Domain)
name = models.CharField(null=False, max_length=255)
value = models.CharField(null=False, max_length=255)
objects = SettingManager()
I built a custom manager on this to override the get() method:
class SettingManager(models.Manager):
def get(self, *args, **kwargs):
from modules.customer.proxies import *
from modules.customer.models import *
object = type('DomainSettings', (SettingProxy,), {'__module__' : 'modules.customer'})()
for pair in self.filter(*args, **kwargs): setattr(object, pair.name, pair.value)
setattr(object, 'domain', Domain.objects.get(id=int(kwargs['domain__exact'])))
return object
This Manager would instantiate an instance of this abstract model. (Abstract models don't have tables so Django doesn't throw up errors)
class SettingProxy(models.Model):
domain = models.ForeignKey(Domain, null=False, verbose_name="Domain")
theme = models.CharField(null=False, default='mytheme', max_length=16)
message = models.CharField(null=False, default='Waddup', max_length=64)
class Meta:
abstract = True
def __init__(self, *args, **kwargs):
super(SettingProxy, self).__init__(*args, **kwargs)
for field in self._meta.fields:
if isinstance(field, models.AutoField):
del field
def save(self, *args, **kwargs):
with transaction.commit_on_success():
Setting.objects.filter(domain=self.domain).delete()
for field in self._meta.fields:
if isinstance(field, models.ForeignKey) or isinstance(field, models.AutoField):
continue
else:
print field.name + ': ' + field.value_to_string(self)
Setting.objects.create(domain=self.domain,
name=field.name, value=field.value_to_string(self)
)
This proxy has all the fields that I'd like display in my ModelFom and store as key-value pairs in my model. Now if I ever needed to add more fields, I could simply modify this abstract model and not have to edit the actual model itself. Now that I have a model, I can simply build a ModelForm on it like so:
class SettingsForm(forms.ModelForm):
class Meta:
model = SettingProxy
exclude = ('domain',)
def save(self, domain, *args, **kwargs):
print self.cleaned_data
commit = kwargs.get('commit', True)
kwargs['commit'] = False
setting = super(SettingsForm, self).save(*args, **kwargs)
setting.domain = domain
if commit:
setting.save()
return setting
I hope this helps. It required a lot of digging through the API docs to figure this out.