Django AttributeError: 'ForwardManyToOneDescriptor' object has no attribute <field> - python

I've got 4 related models in one app:
class AssetType(models.Model):
id = models.AutoField(primary_key=True)
type_name = CaseInsUniqueCharField(name='Type Name')
parent = models.ForeignKey('self', on_delete=models.CASCADE, blank=True, null=True, verbose_name='parent')
type_field = models.ManyToManyField('AssetField', blank=True)
history = HistoricalRecords()
# Return Asset Name for Queries
def __str__(self):
return self.type_name
class AssetField(models.Model):
id = models.AutoField(primary_key=True)
field_name = CaseInsUniqueCharField(name='Field Name')
history = HistoricalRecords()
# Return Field Name for Queries
def __str__(self):
return self.field_name
class AssetFieldValue(models.Model):
id = models.AutoField(primary_key=True)
value = models.CharField('value', blank=True, max_length=100)
field = models.ForeignKey('AssetField', on_delete=models.CASCADE, blank=False)
asset = models.ForeignKey('Asset', on_delete=models.CASCADE, blank=False)
history = HistoricalRecords()
# Return '<Asset> - <Value>' for Queries
def __str__(self):
return str(self.asset) + "-" + str(self.field)
class Asset(models.Model):
id = models.AutoField(primary_key=True)
asset_type = models.ForeignKey('AssetType', on_delete=models.CASCADE, verbose_name='Type')
asset_name = models.CharField('Name', max_length=100)
asset_tag_no = models.CharField('Tag Number', max_length=20, blank=True, null=True)
asset_manufacturer = models.ForeignKey('AssetManufacturer', on_delete=models.SET_NULL, null=True, blank=True)
asset_storage_location = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
asset_list_price = models.PositiveSmallIntegerField('List Price', blank=True, null=True)
asset_needs_pat = models.BooleanField('Needs Electrical Test', null=True)
asset_fields = models.ManyToManyField('AssetField', blank=True, editable=False)
history = HistoricalRecords()
# Return Asset name for queries
def __str__(self):
return self.asset_name
And I have added in a post-save handler just under the Asset Class
#receiver(post_save, sender=Asset)
def my_handler(sender, **kwargs):
t = Asset.asset_type
f = t.type_field.all()
for i in range(1,f.count()+1):
Asset.asset_fields.add(f[i-1:i])
That is supposed to add the AssetFields records that are related to the Asset's AssetType. When I run the following in the django shell I am able to successfully add the relationships to the Asset ManyToMany field asset_fields
>>> a = Asset.objects.first()
>>> t = a.asset_type
>>> f = t.type_field.all()
>>> for i in range(1, f.count()+1):
... a.asset_fields.add(f[i-1:i][0])
However when I save an asset, with the same type t as used in the shell, through the admin page I get the error:
AttributeError at /admin/assets/asset/add/
'ForwardManyToOneDescriptor' object has no attribute 'type_field'
I'm not sure where this error is coming from as the same functions ran in the shell without any issues, unless potentially I'm dealing with the #reciever wrong? Any ideas?

But you don't do the same thing in the view as you do in the shell. In the shell, you access asset_type on a specific instance of Asset. In the view, you call it on the class itself. That doesn't make sense; as with the shell version, you need to query or create an instance, then you can access its asset_type - and call asset_fields.add().
I suspect, since this is a signal handler on Asset, you actually wanted to use the instance that is being saved. In which case:
def my_handler(sender, instance, **kwargs):
t = instance.asset_type
f = t.type_field.all()
I also can't understand what your loop is doing there. I suspect you mean:
for type in t.type_field.all()
instance.asset_fields.add(t)

Related

django getting all objects from select

I also need the field (commentGroupDesc) from the foreign keys objects.
models.py
class commentGroup (models.Model):
commentGroup = models.CharField(_("commentGroup"), primary_key=True, max_length=255)
commentGroupDesc = models.CharField(_("commentGroupDesc"),null=True, blank=True, max_length=255)
def __str__(self):
return str(self.commentGroup)
class Meta:
ordering = ['commentGroup']
class Comment (models.Model):
commentID = models.AutoField(_("commentID"),primary_key=True)
commentUser = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
commentGroup = models.ForeignKey(commentGroup, on_delete=models.CASCADE, null=True)
commentCI = models.ForeignKey(Servicenow, on_delete=models.CASCADE, null=True)
commentText = RichTextField(_("commentText"), null=True, blank=True)
commentTableUpdated = models.CharField(_("commentTableUpdated"), null=True, blank=True, max_length=25)
def __str__(self):
return str(self.commentGroup)
class Meta:
ordering = ['commentGroup']
views.py
comment = Comment.objects.get(pk=commentID)
Here I get the commentGroup fine but I also need commentGroupDesc to put into my form.
At first, it's not a good thing to name same your model field as model name which is commentGroup kindly change field name, and run migration commands.
You can simply use chaining to get commentGroupDesc, also it's better to use get_object_or_404() so:
comment = get_object_or_404(Comment,pk=commentID)
group_desc = comment.commentGroup.commentGroupDesc
Remember to change field and model name first.

Create post_save signal for foreign key fields

I have a profile model which contains experience and education as foreign key fields.
When I access profile template, it throws.
I tried post_save,
def create_education(sender, instance, created, **kwargs):
if created:
Profile.objects.create(education=instance)
post_save.connect(create_education, sender=CustomUser)
it throws this error,
How do I define a post_save signal so experience and education are created when I create a profile?
Note: I double checked the error is because foreign fields are empty i.e there is no error when I add experience and education fields in django admin.
models.py
class Work_Experience(models.Model):
job_title = models.CharField(max_length=100, null=True, blank=True)
company = models.CharField(max_length=100, null=True, blank=True)
description = models.CharField(max_length=300, null=True, blank=True)
exp_start_date = models.DateField(null=True, blank=True)
exp_end_date = models.DateField(null=True, blank=True)
class Education(models.Model):
degree = models.CharField(max_length=100, null=True, blank=True)
school = models.CharField(max_length=100, null=True, blank=True)
edu_start_date = models.DateField(null=True, blank=True)
edu_end_date = models.DateField(null=True, blank=True)
class Profile(models.Model):
experience = models.ForeignKey(Work_Experience, on_delete=models.SET_NULL, null=True, blank=True)
education = models.ForeignKey(Education, on_delete=models.SET_NULL, null=True, blank=True)
forms.py
class ProfileSettingsForm(forms.ModelForm):
job_title = forms.CharField(max_length=40, required=False)
company = forms.CharField(max_length=40, required=False)
description = forms.CharField(max_length=40, required=False)
exp_start_date = forms.DateField(required=False)
exp_end_date = forms.DateField(required=False)
degree = forms.CharField(max_length=40, required=False)
school = forms.CharField(max_length=40, required=False)
edu_start_date = forms.DateField(required=False, input_formats=settings.DATE_INPUT_FORMATS)
edu_end_date = forms.DateField(required=False, input_formats=settings.DATE_INPUT_FORMATS)
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', None)
super(ProfileSettingsForm, self).__init__(*args, **kwargs)
self.fields['job_title'].initial = self.instance.experience.job_title
self.fields['company'].initial = self.instance.experience.company
self.fields['description'].initial = self.instance.experience.description
self.fields['exp_start_date'].initial = self.instance.experience.exp_start_date
self.fields['exp_end_date'].initial = self.instance.experience.exp_end_date
self.fields['degree'].initial = self.instance.education.degree
self.fields['school'].initial = self.instance.education.school
self.fields['edu_start_date'].initial = self.instance.education.edu_start_date
self.fields['edu_end_date'].initial = self.instance.education.edu_end_date
def save(self, commit=True):
model = super(ProfileSettingsForm, self).save(commit=False)
jt = self.cleaned_data['job_title']
co = self.cleaned_data['company']
desc = self.cleaned_data['description']
esd = self.cleaned_data['exp_start_date']
eed = self.cleaned_data['exp_end_date']
degree = self.cleaned_data['degree']
school = self.cleaned_data['school']
edusd = self.cleaned_data['edu_start_date']
edued = self.cleaned_data['edu_end_date']
if model.experience:
model.experience.job_title = jt
model.experience.company = co
model.experience.description = desc
model.experience.exp_start_date = esd
model.experience.exp_end_date = eed
model.experience.save()
else:
model.experience = Work_Experience.objects.create(job_title=jt,
company=co,
description=desc,
exp_start_date=esd,
exp_end_date=eed)
if model.education:
model.education.degree = degree
model.education.school = school
model.education.edu_start_date = edusd
model.education.edu_end_date = edued
model.education.save()
else:
model.education = Education.objects.create(degree=degree,
school=school,
edu_start_date=edusd,
edu_end_date=edued)
if commit:
model.save()
return model
Views.py
class ProfileSettingsView(UpdateView):
model = Profile
form_class = ProfileSettingsForm
pk_url_kwarg = 'pk'
context_object_name = 'object'
template_name = 'profile_settings.html'
def get_success_url(self):
return reverse_lazy('users:profile_settings', args = (self.object.id,))
UPDATE
If I remove the init() method in form, the error resolves. But I don't get the values from database in the form fields once I save it.
How can I rewrite init() method?
def __init__(self, *args, **kwargs):
instance = kwargs.get('instance', None)
super(ProfileSettingsForm, self).__init__(*args, **kwargs)
self.fields['job_title'].initial = self.instance.experience.job_title
self.fields['company'].initial = self.instance.experience.company
self.fields['description'].initial = self.instance.experience.description
self.fields['exp_start_date'].initial = self.instance.experience.exp_start_date
self.fields['exp_end_date'].initial = self.instance.experience.exp_end_date
self.fields['degree'].initial = self.instance.education.degree
self.fields['school'].initial = self.instance.education.school
self.fields['edu_start_date'].initial = self.instance.education.edu_start_date
self.fields['edu_end_date'].initial = self.instance.education.edu_end_date
Why the error?
In the line:
self.fields['job_title'].initial = self.instance.experience.job_title
you're dealing with a Profile instance that does not have a related experience.
How do I define a post_save signal so experience and education are created when I create a profile?
If you want every time you create a Profile it gets populated with am experience and education you should have a signal like:
def create_profile(sender, instance, created, **kwargs):
if created:
experience = Work_Experience.objects.create(profile=instance)
education = Education.objects.create(profile=instance)
post_save.connect(create_profile, sender=Profile)
Why the post_save signal is not triggered when calling the save() of the form?
model = super(ProfileSettingsForm, self).save(commit=False)
According the docs:
This save() method accepts an optional commit keyword argument, which accepts either True or False. If you call save() with commit=False, then it will return an object that hasn’t yet been saved to the database. In this case, it’s up to you to call save() on the resulting model instance. This is useful if you want to do custom processing on the object before saving it, or if you want to use one of the specialized model saving options. commit is True by default.
So by the time you do:
model.experience.job_title = jt
your post_save signal hasn't been triggered and therefore model.experience remains None hence the error:
'NoneType' object has no attribute job_title.
You cannot do that,i recommend you read django docs. just Do this:
Update here
The code bellow work as expected..
from django.conf import settings
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.contrib.auth.base_user import AbstractBaseUser
from latiro_app.managers import UserManager
class User(AbstractBaseUser):
email = models.CharField(verbose_name='email or phone number ', max_length=50, unique=True )
first_name = models.CharField('first name', max_length=15,blank=True)
last_name = models.CharField('last name', max_length=15,blank=True)
country = CountryField(blank=True)
date_joined = models.DateTimeField('date joined', auto_now_add=True)
slug = models.SlugField('slug', max_length=50, unique=True, null=True)
is_active = models.BooleanField('active',default=False)
is_staff = models.BooleanField('staff', default=False)
email_confirmed = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = []
class Meta:
db_table = "users"
permissions = (
("edit_user", "Edit User"),
)
class WorkExperience(models.Model):
job_title = models.CharField(max_length=100, null=True, blank=True)
company = models.CharField(max_length=100, null=True, blank=True)
description = models.CharField(max_length=300, null=True, blank=True)
exp_start_date = models.DateField(null=True, blank=True)
exp_end_date = models.DateField(null=True, blank=True)
class Meta:
db_table = "experience"
def __str__(self):
return (self.job_title)
class Education(models.Model):
degree = models.CharField(max_length=100, null=True, blank=True)
school = models.CharField(max_length=100, null=True, blank=True)
edu_start_date = models.DateField(null=True, blank=True)
edu_end_date = models.DateField(null=True, blank=True)
class Meta:
db_table = "education"
def __str__(self):
return (self.degree)
class Profile (models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete= models.CASCADE,
verbose_name='list of users', null=True)
experience = models.ForeignKey(WorkExperience, on_delete=models.SET_NULL, null=True, blank=True)
education = models.ForeignKey(Education, on_delete=models.SET_NULL, null=True, blank=True)
def __str__(self):
return (self.user)
class Meta:
db_table = "profile"
#receiver(post_save, sender=settings.AUTH_USER_MODEL)
def create_profile(sender, instance, created, **kwargs):
if created and not kwargs.get('raw', False):
profile = Profile(user=instance)
profile.save()
This should work. Tested on my database:
+----+--------------+---------------+---------+
| id | education_id | experience_id | user_id |
+----+--------------+---------------+---------+
| 1 | NULL | NULL | 2 |
+----+--------------+---------------+---------+
The null values on education_id and experience_id will be update by user_id instance when updating profile.
Now User can update his/her profile like this:
note:i'm not using signal.
#Form.py
class EducationForm(forms.ModelForm):
degree = forms.CharField(max_length=40, required=False)
school = forms.CharField(max_length=40, required=False)
edu_start_date = forms.DateField(required=False,
input_formats=settings.DATE_INPUT_FORMATS)
edu_end_date = forms.DateField(required=False,
input_formats=settings.DATE_INPUT_FORMATS)
class Meta:
model= Education
fields =["degree","school", "edu_start_date","edu_start_date"]
#View.py
class EducationFormView(UpdateView):
model = Education
form_class = EducationForm
template_name = 'latiro_app/education_form.html'
def get_success_url(self):
return reverse_lazy('users:profile_settings',
args =(self.object.id,))
def get(self, form, ** kwargs):
profile_instance = get_object_or_404(self.model, user_id=self.kwargs['pk'])
#Pass user profile data to the form
context = {'form':self.form_class(instance=profile_instance)}
if self.request.is_ajax():
kwargs['ajax_form'] = render_to_string(self.template_name, context, request=self.request )
return JsonResponse(kwargs)
else:
return render(self.request, self.template_name, context)

Django - Get name of model object by iterating through class

I'm new to Python (and pretty inexperienced at programming in general), but I have an issue I can't for the life of me figure out.
I'm trying to pre-populate a field in my database with the name of an instance from another model/class in my database. The model with the field I want to pre-populate is an "instance" of the model instance from which I'm trying to grab the name, and has a foreign key to that instance.
Goal: 1) User selects the parent of the object by assigning the foreign key to the parent 2) A function grabs the name of the parent instance matching the foreign key the user selected. 3) the result from that function is used as the default value for the field.
Here's the code:
class Injury(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular injury')
name = models.CharField(max_length=200, help_text='Enter an injury or complication (e.g. respiratory failure)')
description = models.TextField(max_length=1000, blank=True, help_text='Describe the injury')
time_to_onset = models.PositiveIntegerField(blank=True, validators=[MaxValueValidator(10000)], help_text='Enter expected time from trigger until injury/complication onset')
findings = models.TextField(max_length=1000, blank=True, help_text='Enter the signs and symptoms of the injury or complication')
vitals_trends = models.TextField(max_length=1000, blank=True, help_text='Enter the vitals changes due to the injury')
class Meta:
ordering = ['name']
def __str__ (self):
return self.name
def get_absolute_url(self):
return reverse('Injury-detail', args=[str(self.id)])
class Injury_Instance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular injury')
parent_case = models.ForeignKey(PFC_Case, on_delete=models.SET_NULL,null=True)
injury_model = models.ForeignKey(Injury, on_delete=models.SET_NULL, null=True)
def set_injury_name(self):
for injuries in Injury.all()
if injury_model == injuries
break
return Injury.name
name = dislay_models.CharField(default=set_injury_name(self,Injury,injury_model), max_length=100)
triggered_by = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
def __str__ (self):
return self.name
def get_absolute_url(self):
return f'{self.id} ({self.Injury.name})'
The problem area is def set_injury_name and Injury_Instance.name
Thanks!!!
Edit:
I tried the following code, but I'm getting the error 'NameError: name 'self' is not defined'
class Injury_Instance(models.Model):
id = models.UUIDField(primary_key=True, default=uuid.uuid4, help_text='Unique ID for this particular injury')
parent_case = models.ForeignKey(PFC_Case, on_delete=models.SET_NULL,null=True)
injury_model = models.ForeignKey(Injury, on_delete=models.SET_NULL, null=True)
def get_injury_name(self):
return self.name
injury_name=get_injury_name(self)
name = models.CharField(default=injury_name, max_length=100)
triggered_by = models.ForeignKey('self', on_delete=models.SET_NULL, null=True, blank=True)
def __str__ (self):
return self.name
def get_absolute_url(self):
return f'{self.id} ({self.Injury.name})'
You don't need to store the name of your foreign key field as you always can access it with:
self.injury_model.name
If you need to get access it just by name you can write a property in Injury_Instance model.
#property
def name(self):
return self.injury_model.name

Django: many to many circular reference

I have a problem in my models because I have a circular reference in a many to many relationship
This is a part of my web app, the Sessions are of a group (a group can have more than one session) and each Session have more than one assistant but the assistants only can be members of the session's group
Here are my models:
class GroupMember(models.Model):
member = models.ForeignKey(Volunteer, null=True, blank=True)
group = models.ForeignKey(Group)
leader = models.BooleanField(default=False)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{}'.format(self.member)
class Session(models.Model):
name = models.CharField(max_length=250)
group = models.ForeignKey(Group)
assistants = models.ManyToManyField(GroupMember,through=SessionAssistant)
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{}'.format(self.name)
class SessionAssistant(models.Model):
assistant = models.ForeignKey(GroupMember)
session = models.ForeignKey(Session, null=True, blank=True, on_delete=models.CASCADE)
assist = models.BooleanField(default=True)
ability = models.IntegerField(null=True,
blank=True,
validators=[MaxValueValidator(5), MinValueValidator(1)])
performance = models.IntegerField(null=True,
blank=True,
validators=[MaxValueValidator(5), MinValueValidator(1)])
created = models.DateTimeField(auto_now_add=True)
def __str__(self):
return '{}'.format(self.assistant)
What happens if you change assistants = models.ManyToManyField(GroupMember,through=SessionAssistant) to assistants = models.ManyToManyField(GroupMember,through='SessionAssistant')?

Using one form for two similar models

I have the following models:
class BaseAddress(models.Model):
name = models.CharField(max_length=100)
business_name = models.CharField(max_length=100, blank=True, null=True)
address = models.CharField(max_length=100, blank=True, null=True)
address_2 = models.CharField(max_length=100, blank=True, null=True)
address_3 = models.CharField(max_length=100, blank=True, null=True)
city = models.CharField(max_length=100, blank=True, null=True)
state = models.CharField(max_length=2, blank=True, null=True)
zip_code = models.CharField(max_length=10, blank=True, null=True)
phone = models.CharField(max_length=30, blank=True, null=True)
class Meta:
abstract = True
class ProfileBilling(BaseAddress):
profile = models.OneToOneField(
Profile, related_name='billing_info')
class OrderBilling(BaseAddress):
order = models.OneToOneField(Order, related_name='billing')
name_on_card = models.CharField(max_length=100)
#card_type = models.PositiveSmallIntegerField(
# choices=CARD_TYPE, default=0)
#card_number = models.CharField(
# max_length=16, default=0)
expire_month = models.PositiveSmallIntegerField(
choices=MONTHS, null=True, default=0)
expire_year = models.PositiveSmallIntegerField(
choices=YEARS, null=True, default=1960)
When customers input a billing address, I want to save it in OrderBilling, but I also want to save it in ProfileBilling as their most recent billing address. How do I do so?
How do I go about using forms to save billing address in two different tables when the OrderBilling and ProfileBilling have most of the same fields...?
How do I do this in Django?
Here is my OrderBilling form:
class OrderBillingForm(forms.ModelForm):
class Meta:
model = OrderBilling
exclude = ('order',)
def __init__(self, *args, **kwargs):
super(OrderBillingForm, self).__init__(*args, **kwargs)
self.fields['address'].required = True
self.fields['city'].required = True
self.fields['state'] = USStateField()
self.fields['zip_code'] = us.USZipCodeField()
self.fields['phone'].required = False
self.fields['expire_month'].required = False
self.fields['expire_year'].required = False
def clean(self):
return self.cleaned_data
You can override save() method, But the smarter way in your case would be using post_save signal for this purpose.
After aOrderBilling get saved, You can save its data into ProfileBilling too.
look at some example on google search in case to be familiar with post_save signal,
like:
https://groups.google.com/forum/?fromgroups=#!topic/django-users/2m88qTrqnM8
http://www.djangofoo.com/85/signal-connect-disconnect-django-signals
etc...
then Easyily in your post_save receiver|callback funcstion get the OrderBilling instance
orderbil_instance = kwargs['instance']
And create your ProfileBilling from its data
ProfileBilling.objects.create(name=orderbil_instance.name, ....)
Something like on post_save signal receiver
def do_something(sender, **kwargs):
# Getting OrderBilling instance which get saved just now
orderbil_instance = kwargs['instance']
# Save the data into new ProfileBilling
ProfileBilling.objects.create(name=orderbil_instance.name, ....)

Categories