Django - Retrieve or get data from selected foreign key field - python

Relatively new to Django, I'm working on a Django project and attempting to retrieve particular foreign key object into variable when it's selected in Form.
model.py
class item_category(models.Model):
idItemCat = models.CharField(primary_key=True max_length=5)
nameCategory = models.CharField(max_length=150)
def __str__(self):
return self.nameCategory
class item_code(models.Model):
idItemCat = models.ForeignKey(item_category, on_delete=models.DO_NOTHING)
idItemCode = models.CharField(primary_key=True, editable=False, max_length=20)
def __str__(self):
return self.idItemCode
I know I could retrieve object with making QuerySet such as .objects.last() and .objects.filter() or else, but it's just retrieve objects from database or existing data. What I'm about to do is, when a user submit a new data it'll retrieve particular foreign key object based on what I'm selected in this Form, so I could put into variable.
Any idea how should do it? it would be so much appreciated.

You really should look at the Django Docs on forms. They are excellent, and will teach you the right way to handle forms, a complicated topic.
To answer your question directly, it looks like you already have the html part, and as long as it has the form tag, like this:
<form action='your_view' method='post'>
...
</form>
Then in your view you could do something like this:
def your_view(request):
if request.method == 'POST':
category = item_category.objects.get(pk=request.POST.get('name_attribute_of_your_input'))
I'd need to see more specifics to give you a better answer, but there are several issues you should fix first.
First, your class names should be capitalized, field names lower case, and second,are you sure you want to make a CharField the primary key? You can, but for most cases, the automatically generated integer pk that Django creates is best.
class ItemCategory(models.Model):
# Django will create an integer pk for you
# idItemCat = models.CharField(primary_key=True max_length=5)
nameCategory = models.CharField(max_length=150)
def __str__(self):
return self.nameCategory
class ItemCode(models.Model):
idItemCat = models.ForeignKey(ItemCategory, on_delete=models.DO_NOTHING)
# Again, just let Django generate the primary key
# idItemCode = models.CharField(primary_key=True, editable=False, max_length=20)
def __str__(self):
return self.idItemCode

Related

Multiple search with django foreign key (Related Field got invalid lookup: icontains)

I understand that you can't directly use icontains on a foreign key when searching but I haven't found a solution yet.
Here is my search view in views.py (I have imported every model needed):
def search(request):
# if the user actually fills up the form
if request.method == "POST":
searched = request.POST['searched']
# author__icontains part is not working
posts = Post.objects.filter(Q(title__icontains=searched) | Q(author__author__icontains=searched))
return render(request, 'blog/search.html', {'searched': searched, 'posts': posts})
else:
return render(request, 'blog/search.html', {})
Here is my model in model.py:
class Post(models.Model):
title = models.CharField(max_length=100)
content = models.TextField()
date_posted = models.DateTimeField(default=timezone.now)
author = models.ForeignKey(User, on_delete=models.CASCADE)
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('post-detail', kwargs={'pk': self.pk})
Mainly, this is not working:
posts = Post.objects.filter(Q(title__icontains=searched) | Q(author__author__icontains=searched))
The error is Related Field got invalid lookup: icontains
author is a User object. Therefore you should work with username, or first_name, or some other field. Likely author is also the value of a related_name=… [Django-doc] that thus makes a LEFT OUTER JOIN on another table, and thus would work on the primary key(s) of that table.
You thus filter with:
def search(request):
# if the user actually fills up the form
if request.method == 'POST':
searched = request.POST['searched']
# author__icontains part is not working
posts = Post.objects.filter(
Q(title__icontains=searched) |
Q(author__username__icontains=searched)
)
return render(request, 'blog/search.html', {'searched': searched, 'posts': posts})
return render(request, 'blog/search.html')
Note: It is normally better to make use of the settings.AUTH_USER_MODEL [Django-doc] to refer to the user model, than to use the User model [Django-doc] directly. For more information you can see the referencing the User model section of the documentation.
Note: Searching is usually done through a GET rquest, since that means the query is stored in the querystring and thus the URL. This makes it convenient to for example share the URL with the query to someone else, or bookmark the result. POST requests are usually used for state-changing actions, or for requests with sensitive data.
This problem is caused by Django's inability to handle foreign keys with multiple values for a single field. The reason for this limitation is that Django doesn't know how to resolve these conflicts, so it simply ignores them. In your case, since there are two fields in your model that match the search criteria, Django will ignore both of those results and display an empty list.
To fix this issue, we need to add a new attribute to our model called "icontains" which would contain the value of the other field. Then, we'll set this attribute as a default value for the "author" field when querying from the database. Here is what your model should look like now:
class Post(models.Model): title = models.CharField(max_length=100) content = models.TextField() date_posted = models.DateTimeField(default=timezone.now) author = models.ForeignKey(User, on_delete=models.CASCADE) icontains = models.CharField(max_length=100, null=True, blank=True) def __str__(self): return self.title def get_absolute_url(self): return reverse('post-detail', kwargs=dict(pk=self.pk))
With this change, the code will work properly.
For more information about this limitation, see the Django documentation here: https://docs.djangoproject.com/en/1.9/topics/db/queries/#lookups-that-span-relationships

Django rest Framework: How to create a custom, auto-generating string field?

I'm using the generic Django Rest Framework classes for an application, but I'd like to be able to auto-generate a short customer order ID whenever a new order instance is created.
The generic classes are awesome, but small tweaks can get messy fast. I'm wondering if there's a way to do the auto-generation in models.py, in the spirit of classes like AutoIncrement or the way the primary key integer is created (but not as a primary key).
Let's say I have a model
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
details = models.CharField(null=True, max_length=200)
#property
def title(self):
return f"{self.user.first_name}'s, order"
and I want to add a new field, order_reference. And lets say I want to add some custom logic, like
#before_createish_thing
def order_reference(self):
# Return something like XYZ2021-000-23
return f"XYZ-{datetime.now().year}-000-{self.order_id}"
somewhere.
I don't suppose there's anything like a before_create decorator along the same lines as the #property decorator?
My view is as simple as
class OrderList(generics.ListCreateAPIView):
queryset = Order.objects.all()
serializer_class = OrderSerializer
hence my reluctance to hack it apart just to get to the create() method.
Is there any way to do this, ideally by somehow placing the logic in the model file?
You could simply do something like this:
class Order(models.Model):
user = models.ForeignKey(User, on_delete=models.CASCADE)
details = models.CharField(null=True, max_length=200)
creation_date = models.DateField(auto_now_add=True)
#property
def title(self):
return f"{self.user.first_name}'s, order"
#property
def order_reference(self):
return f"XYZ-{self.creation_date.year}-000-{self.order_id}"
Also storing the date can allow you to change later your order_reference property and have access to the day, month, year for example. Or even use a DatetimeField. It always good to have the creation date, for future requirements.

Can't disable ForeignKey referential integrity check in Django 1.9

I have a model with two entities, Person and Code. Person is referenced by Code twice, a Person can be either the user of the code or the approver.
What I want to achieve is the following:
if the user provides an existing Person.cusman, no further action is needed.
if the user provides an unknown Person.cusman, a helper code looks up other attributes of the Person (from an external database), and creates a new Person entity.
I have implemented a function triggered by pre_save signal, which creates the missing Person on the fly. It works fine as long as I use python manage.py shell to create a Code with nonexistent Person.
However, when I try to add a new Code using the admin form or a CreateView descendant I always get the following validation error on the HTML form:
Select a valid choice. That choice is not one of the available choices.
Obviously there's a validation happening between clicking on the Save button and the Code.save() method, but I can't figure out which is it. Can you help me which method should I override to accept invalid foreign keys until pre_save creates the referenced entity?
models.py
class Person(models.Model):
cusman = models.CharField(
max_length=10,
primary_key=True)
name = models.CharField(max_length=30)
email = models.EmailField()
def __unicode__(self):
return u'{0} ({1})'.format(self.name, self.cusman)
class Code(models.Model):
user = models.ForeignKey(
Person,
on_delete=models.PROTECT,
db_constraint=False)
approver = models.ForeignKey(
Person,
on_delete=models.PROTECT,
related_name='approves',
db_constraint=False)
signals.py
#receiver(pre_save, sender=Code)
def create_referenced_person(sender, instance, **kwargs):
def create_person_if_doesnt_exist(cusman):
try:
Person = Person.objects.get(pk=cusman)
except Person.DoesNotExist:
Person = Person()
cr = CusmanResolver()
Person_details = cr.get_person_details(cusman)
Person.cusman = Person_details['cusman']
Person.name = Person_details['name']
Person.email = Person_details['email']
Person.save()
create_Person_if_doesnt_exist(instance.user_id)
create_Person_if_doesnt_exist(instance.approver_id)
views.py
class CodeAddForm(ModelForm):
class Meta:
model = Code
fields = [
'user',
'approver',
]
widgets = {
'user': TextInput,
'approver': TextInput
}
class CodeAddView(generic.CreateView):
template_name = 'teladm/code_add.html'
form_class = CodeAddForm
You misunderstood one thing: You shouldn't use TextField to populate ForeignKey, because django foreign keys are populated using dropdown/radio button to refer to the id of the object in another model. The error you got means you provided wrong information that doesn't match any id in another model(Person in your case).
What you can do is: not using ModelForm but Form. You might have some extra work to do after you call form.is_valid(), but at least you could code up your logic however you want.

Django: combine two ForeignKeys into one field

I need to implement the following:
The user shall be presented with a form that will have a drop down choice menu consisting of property names. There are two types of properties: general properties, i.e. properties common for all users and custom properties, i.e. properties that each user has defined prior to that. The models would look something like that:
class GeneralPropertyName(models.Model):
name = models.CharField(max_length=20)
class CustomPropertyName(models.Model):
user = models.ForeignKey(User)
name = models.CharField(max_length=20)
The drop down menu should have all general properties and only those custom properties that pertain to the user.
First question: how to define such a model?
I need to: 1. somehow unify both properties, 2. take only those items from CustomPropertyName that pertain to the user
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(GeneralPropertyName) ??UNIFY??? ForeignKey(CustomPropertyName)
Second, is there anything special that needs to be done with ModelForm?
class SpecDataForm(ModelForm):
class Meta:
model = SpecData
And the 3rd question is what needs to be done in the view? I will need to use inline formsets since I will have a few dynamic forms like that.
def index(request):
user = User.objects.get(username=request.user.username)
specdataFormSet = inlineformset_factory(User, SpecData, form=SpecDataForm, extra=30)
...
specdata_formset = specdataFormSet(instance=user, prefix='specdata_set')
...
Thanks.
EDIT: Adjusted juliocesar's suggestion to include formsets. Somehow I am getting the following error message: Cannot resolve keyword 'property' into field. Choices are: id, name, selection_title, user
def index(request):
user = User.objects.get(username=request.user.username)
user_specdata_form = UserSpecDataForm(user=user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
You can use a GenericForeignKey to handle it, but you still need more to solve your further questions about forms and view.
I have made an example of how you solve your problem (logged user can select from General properties and his Custom properties, non-logged user only can select General properties). I used model inheritance for the properties (In your sample code it seems that a CustomPropertyName is a PropertyName with other fields). I think inheritance is an easier and a more basic concept than ContentTypes and it fits to your needs.
NOTE: I remove some code like imports to simplify the code.
1) models.py file:
class PropertyName(models.Model):
name = models.CharField(max_length=20)
def __unicode__(self):
return self.name
class CustomPropertyName(PropertyName): # <-- Inheritance!!
user = models.ForeignKey(User)
def __unicode__(self):
return self.name
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = models.ForeignKey(PropertyName)
NOTES: The field SpecData.property points to PropertyName since all properties are saved in the PropertyName's database table.
2) forms.py file:
from django import forms
from django.db.models import Q
from models import SpecData, PropertyName
def UserSpecDataForm(user=None):
UserPropertiesQueryset = PropertyName.objects.filter(Q(custompropertyname__user=None) | Q(custompropertyname__user__id=user.id))
class SpecDataForm(forms.ModelForm):
property = forms.ModelChoiceField(queryset=UserPropertiesQueryset)
class Meta:
model = SpecData
exclude = ('user',)
return SpecDataForm
NOTES: The trick here is to generate the form SpecDataForm dynamically, by filtering properties according the user specified in the parameter.
3) views.py file:
from forms import UserSpecDataForm
def index(request):
if request.POST:
form = UserSpecDataForm(request.user)(request.POST) # instance=user
if form.is_valid():
spec_data = form.save(commit=False)
spec_data.user = request.user
spec_data.save()
else:
form = UserSpecDataForm(request.user)()
return render_to_response('properties.html', {'form': form}, context_instance=RequestContext(request))
NOTES: Nothing special here, just a call to form.UserSpecDataForm(request.user) that returns the form class and then instantiate. Also setted the logged-in user to the object returned on save since It was excluded in the form to not show in front-end.
Following this basic example you can do the same with formsets if you need it.
UPDATE:
Formset can be used by adding following code to the view:
user_specdata_form = UserSpecDataForm(user=request.user)
SpecdataFormSet = inlineformset_factory(User, SpecData, form=user_specdata_form, extra=30)
The complete project sample can be downloaded from http://ge.tt/904Wg7O1/v/0
Hope this helps
1a) have you looked into django's ContentType framework this will allow you to have generic foreign keys and you can put restrictions on what types of models are acceptable to store in.
1b) I think that the validation for accepting what type of foreign key is acceptable shouldn't be in your model but should be part of your form validation before saving.
2) If you do use a model form you're going to have to define your own custom widget for the propery field. This means you're probably going to have to write you're own render function to render the html from the field. You should also define your own validation function on the form to make sure that only the appropriate data is acceptable to save.
3) I don't think you'll have to do anything you aren't already doing in the views
Use GenericForeignKey:
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
property = GenericForeignKey('content_type', 'object_id')
You can use this to combine the two fields(type & id) into a single choice field.
One way is that you have only one model, make user nullable:
class PropertyName(models.Model):
user = models.ForeignKey(User, null=True, blank=True)
name = models.CharField(max_length=20)
class SpecData(models.Model):
user = models.ForeignKey(User)
selection_title = models.CharField(max_length=20)
property = ForeignKey(PropertyName)
So, if user is not set, it is a general property. If it is set, it is related to this user.
However, please note that if you need unique property names, that NULL != NULL.
Of course, the suggested GenericForeignKey solution is better for some cases.
Also, you can easily make the normal (non-model) form with that you describe and separate form logic from model logic.

Django Forms: Foreign Key in Hidden Field

My form:
class PlanForm(forms.ModelForm):
owner = forms.ModelChoiceField(label="",
queryset=Profile.objects.all(),
widget=forms.HiddenInput())
etc...
class Meta:
model = Plan
Owner, in the model, is a ForeignKey to a Profile.
When I set this form, I set the value of "owner" to be a Profile object.
But when this comes out on the form, it seems to contain the name of the Profile like this:
<input type="hidden" name="owner" value="phil" id="id_owner" />
When the form is submitted and gets back to my views.py I try to handle it like this:
form = PlanForm(request.POST)
...
if form.is_valid():
plan = form.save()
return HttpResponseRedirect('/plans/%s'%plan.id) # Redirect after POST
However, what I get is a type-conversion error as it fails to turn the string "phil" (the user's name that was saved into the "owner" field) into an Int to turn it into the ForeignKey.
So what is going on here. Should a ModelForm represent a foreign key as a number and transparently handle it? Or do I need to extract the id myself into the owner field of the form? And if so, how and when do I map it back BEFORE I try to validate the form?
I suspect that the __unicode__ method for the Profile model instance, or the repr thereof is set to return a value other than self.id. For example, I just set this up:
# models.py
class Profile(models.Model):
name = models.CharField('profile name', max_length=10)
def __unicode__(self):
return u'%d' % self.id
class Plan(models.Model):
name = models.CharField('plan name', max_length=10)
profile = models.ForeignKey(Profile, related_name='profiles')
def __unicode__(self):
return self.name
# forms.py
class PlanForm(forms.ModelForm):
profile = forms.ModelChoiceField(queryset=Profile.objects.all(),
widget=forms.HiddenInput())
class Meta:
model = Plan
# views.py
def add_plan(request):
if request.method == 'POST':
return HttpResponse(request.POST['profile'])
profile = Profile.objects.all()[0]
form = PlanForm(initial={'profile':profile})
return render_to_response('add_plan.html',
{
'form':form,
},
context_instance=RequestContext(request))
With that, I see PlanForm.profile rendered thus in the template:
<input type="hidden" name="profile" value="1" id="id_profile" />
Hmm...
This might actually be a security hole.
Suppose a malicious attacker crafted a POST (say, by using XmlHttpRequest from FireBug) and set the profile term to some wacky value, like, your profile ID. Probably not what you wanted?
If possible, you may want to get the profile from the request object itself, rather than what's being submitted from the POST values.
form = PlanForm(request.POST)
if form.is_valid():
plan = form.save(commit=False)
plan.owner = request.user.get_profile()
plan.save()
form.save_m2m() # if neccesary
When you assign a Profile object to the form, Django stringifies it and uses the output as the value in the form. What you would expect though, is for Django to use the ID of the object instead.
Luckily, the workaround is simple: Just give the form primary key values of the Profile objects instead:
form = PlanForm(initial={'profile': profile.pk})
On the other end, when you're working with bound forms, however, they work much more sensibly:
form = PlanForm(request.POST)
if form.is_valid():
print form.cleaned_data['profile'] # the appropriate Profile object
There's usually no need to put related object into form field. There's a better way and this is specifying parent id in form URL.
Let's assume you need to render a form for new Plan object and then create one when form is bubmitted. Here's how your urlconf would look like:
(r"/profile/(?P<profile_id>\d+)/plan/new", view.new_plan), # uses profile_id to define proper form action
(r"/profile/(?P<profile_id>\d+)/plan/create", view.create_plan) # uses profile_id as a Plan field
And if you're changing existing object, all you need is plan_id, you can deduce any related record from it.
Since ModelChoiceField inherits from ChoiceFIeld, you should use the MultipleHiddenInput widget for this:
class PlanForm(forms.ModelForm):
owner = forms.ModelChoiceField(
queryset=Profile.objects.all(),
widget=forms.MultipleHiddenInput())
class Meta:
model = Plan

Categories

Resources