Django: Create model instance within model - django

I have another probably quite basic Django question. I am writing a database app for my classes, and I would like to be able to set the current year from within the app. The idea behind this is that I can manually switch to the next academic year when all the marking etc is done. The function to switch the current year is then supposed to do other things as well - put the students in the new default courses etc.
So I created a model called MetaData:
class MetaData(models.Model):
data_id = models.IntegerField(default = 1, unique = True)
current_year = models.IntegerField(choices=ACADEMIC_YEARS)
It would be great if I could access the current_year in other models, for example to set it as default when I want to enter new courses/students. I tried this:
class Course(models.Model):
try:
meta = MetaData.objects.get(data_id=1)
current_year = meta.current_year
except MetaData.DoesNotExist:
current_year = 2010
year = models.IntegerField(choices=ACADEMIC_YEARS, default=current_year)
Unfortunately, I get the error "No such table: database_metadata" when I run syncdb for the first time - I would guess because syncdb has not created the tables when it tests the code.
Is there a better solution for this?

Either put the constant inside your code (if you could hard code the logic of computing academic years. Or always remember to change it manually, which sounds OK as you've already hard-coded ACADEMIC_YEARS), or in DB as danodonovan suggests. Or model academic years directly w/o storing a constant in MetaData
from django.core.exceptions import ValidationError
class AcademicYear(models.Model):
start = models.DateField() # or simply year
end = models.DateField()
is_current = models.BooleanField()
class Meta:
ordering = ('-is_current', 'start')
def clean(self): # only allow at most one current year
if self.is_current and \
self.__class__.objects.exclude(pk=self.pk).filter(is_current=True).exists():
raise ValidationError('Found another `current` academic year.'
def __unicode__(self):
return 'blahblah'
# then in Course
class Course(models.Model):
academic_year = models.ForeignKey(AcademicYear)
Thus in the Admin, you could easily step up AcademicYears and display them as choices of the academic_year field of the Course.
If you still would like to use MetaData, just change the current_year to be a function:
def get_current_year():
try:
return MetaData.objects.get(data_id=1).current_year
except MetaData.DoesNotExist:
return 2010
class Course(models.Model):
year = models.IntegerField(choices=ACADEMIC_YEARS, default=get_current_year)

This sounds like you need to use the ForeignKey field to link your models.
class MetaData(models.Model):
current_year = models.IntegerField(choices=ACADEMIC_YEARS)
class Course(models.Model):
year = models.ForeignKey(MetaData, default=this_year)
def this_year(self):
return MetaData.objects.get(current_year=datetime.now().year)
To set the default year I've added a this_year function to the MetaData model. The Course models year attribute will call this when setting the default year.
Having said that, it would be much simpler if you used a DateField for simple dates.

class Boat_Owner(models.Model):
username = models.CharField(max_length=200)
password = models.CharField(max_length=200)
name = models.CharField(max_length=200,unique=True,
verbose_name="Boat Owner Name")
address = models.CharField(max_length=200)
phone = models.CharField(max_length=200)
email = models.EmailField('email address',
unique=True, db_index=True)
avatar = models.ImageField('profile picture',
upload_to='static/media/images/avatars/',
null=True, blank=True)
def set_avatar(self):
self.has_picture = True
def __str__(self):
return u'%s %s %s %s %s %s' % (self.id,self.name,self.username,self.password,self.address,self.email)
class Meta:
verbose_name = "Boat Owner"
verbose_name_plural = "Boat Owner"

Related

Django referencing a specific object in a many-to-one relationship

Let's say I have the following models:
from django.db import models
class Reporter(models.Model):
first_name = models.CharField(max_length=30)
last_name = models.CharField(max_length=30)
email = models.EmailField()
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
I'd like to add a favorite_article field to my Reporter model that will reference a specific Article from reporter.articles.
One option is put the information into the Article model instead:
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
is_favorite = models.BooleanField()
But this doesn't seem like a very clean solution. Is there a better method to do this?
The approach you've suggested will work, however in its current form it allows for multiple Articles to be the favorite of one Reporter. With a bit of extra processing you can ensure that only one (at most) Article per Reporter is the favorite.
Making a few modifications to a couple of the answers to the question Unique BooleanField value in Django? we can restrict one True value per Reporter rather than one True value for the entire Article model. The approach is to check for other favorite Articles for the same Reporter and set them to not be favorites when saving an instance (rather than using a validation restriction).
I'd also suggest using a single transaction in the save method so that if saving the instance fails the other instances are not modified.
Here's an example:
from django.db import transaction
class Article(models.Model):
headline = models.CharField(max_length=100)
pub_date = models.DateField()
reporter = models.ForeignKey(Reporter, related_name="articles", on_delete=models.CASCADE)
is_favorite = models.BooleanField(default=False)
def save(self, *args, **kwargs):
with transaction.atomic():
if self.is_favorite:
reporter_id = self.reporter.id if self.reporter is not None else self.reporter_id
other_favorites = Article.objects.filter(is_favorite=True, reporter_id=reporter_id)
if self.pk is not None: # is None when creating a new instance
other_favorites.exclude(pk=self.pk)
other_favorites.update(is_favorite=False)
return super().save(*args, **kwargs)
I've also changed the approach to use a filter rather than a get just in case.
Then to get the favorite article for a reporter, you can use:
try:
favorite_article = reporter.articles.get(is_favorite=True)
except Article.DoesNotExist:
favorite_article = None
which you could wrap into a method/property of the Reporter class.

In Django REST framework, how do you access a field in a table referenced by a foreign key

I have the following models in Django:
class campaign(models.Model):
start_date = models.DateField('Start Date')
end_date = models.DateField('End Date')
description = models.CharField(max_length=500)
name = models.CharField(max_length=200)
active_start_time = models.TimeField()
active_end_time = models.TimeField()
last_updated = models.DateTimeField('Date updated',auto_now=True)
active = models.BooleanField(default=True)
client_id = models.ForeignKey('client',on_delete=models.PROTECT)
def __unicode__(self):
return u'%d | %s | %s' % (self.id,self.name, self.description)
class campaign_product(models.Model):
product_id = models.ForeignKey('product',on_delete=models.PROTECT)
last_updated = models.DateTimeField('Date updated',auto_now=True)
campaign_id = models.ForeignKey('campaign',on_delete=models.PROTECT)
class product(models.Model):
name = models.CharField(max_length=200)
description = models.CharField(max_length=500)
sku = models.CharField(max_length=200,blank=True,null=True)
retail_price = models.DecimalField(decimal_places=2,max_digits=11)
discount_price = ((1,'Yes'),(0,'No'))
discounted_price = models.DecimalField(decimal_places=2,max_digits=11,blank=True,null=True)
category_id = models.ForeignKey('category',on_delete=models.PROTECT)
last_updated = models.DateTimeField('Date updated',auto_now=True)
active = models.BooleanField(default=True)
def __unicode__(self):
return u'%d | %s' % (self.id, self.name)
I also have the following serializer:
class campaignProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = campaign_product
fields = ('product_id', 'campaign_id')
And the following view set behavior in the urls.py file:
class campaignProductViewSet(viewsets.ModelViewSet):
queryset = campaign_product.objects.filter(campaign_id__start_date__lte=datetime.now(),campaign_id__end_date__gte=datetime.now(),campaign_id__active__exact=True)
serializer_class = campaignProductSerializer
My problem is I need to include the name field from the products model in my query results when for instance a request is made on http://127.0.0.1:8000/campaign_product/1/. Currenly this request returns only the product_id and the campaign_id. I tried making the serializer as follows:
class campaignProductSerializer(serializers.HyperlinkedModelSerializer):
class Meta:
model = campaign_product
fields = ('product_id', 'campaign_id', 'product.name')
But then the service returns the following error:
Field name `product.name` is not valid for model `campaign_product`.
I event tried using product__name with and without quotes. Without quotes it tells me that there is no such variable, and with quotes it gives the is not valid for model error similar to the above. Heeelp! Getting this extra field is proving to be a pain :-(
What you want will need to look something more like this:
class campaignProductSerializer(serializers.HyperlinkedModelSerializer):
product_name = serializers.CharField(source='product_id.name')
class Meta:
model = campaign_product
fields = ('product_id', 'campaign_id', 'product_name')
P.S. As an unrelated side note, it is generally a convention in Python code to name classes with CamelCase, such as Campaign, CampaignProduct, Product, and CampaignProductSerializer.
Edit: P.P.S. Originally, I had put written the product_name field with source='product.name'. This was actually due to me looking at the code too quickly and making assumptions based on Django conventions. Typically, with a Django ForeignKey, you would name the ForeignKey field after the model you are linking to, rather than explicitly naming it with _id. For example, the CampaignProduct model would typically be written with product = ForeignKey(...) and campaign = ForeignKey(...). In the background, Django will actually use product_id and campaign_id as the database field names. You also have access to those names on your model instances. But the product and campaign variables on your model instances actually return the objects which you are referring to. Hopefully that all makes sense.

How to write a custom save method for a table to add data to a different table?

I am very new to django. I am trying to implement small application for my business which is a rental service for a football field which is made of artificial grass. Service is based on hourly rental of the field. People can rent the field hourly or they can chose membership option which includes specific day and hour in a week for a defined period. I have two classes in my model.
One is for one-time reservation. People can reserve the football field for any day and hour in a week. This is very simple.
class Reservation(models.Model):
name = models.ForeignKey(User)
membership = models.ForeignKey((Member), blank=True, null=True, editable=False)
date = models.DateField()
field_name = models.ForeignKey(Field)
fee = models.DecimalField(decimal_places=0, max_digits=3)
is_member = models.BooleanField(default=False, editable=False)
is_active = models.BooleanField(default=True)
def __unicode__(self):
return u'%s %s %s %s' % (self.date, self.user.first_name,self.user.last_name, self.field)
class Meta:
unique_together = (("date", "field"))
Another for membership. People can apply to become a member. When somebody apply for a member ship, i want to create reservations for that day and hour between start and end dates automatically so there will not be any one-time reservation at that day and hour. I need to write a custom save method but i could not find how to add information to reservation table while saving membership table. I want to add member name and make is_member field to true in order to see that it is a member reservation.
class Member(models.Model):
DAY_CHOICES = (
(1, 'PAZARTESI'),
(2, 'SALI'),
(3, 'CARSAMBA'),
(4, 'PERSEMBE'),
(5, 'CUMA'),
(6 ,'CUMARTESI'),
(7, 'PAZAR'),
)
name = models.ForeignKey(User)
starting_date = models.DateField()
end_date = models.DateField()
day = models.IntegerField(max_length=1, choices=GUN_CHOICES)
field = models.ForeignKey(Field)
fee = models.DecimalField(decimal_places=0, max_digits=3)
def __unicode__(self):
return u'%s %s, %s, %s' % (self.name.first_name, self.name.last_name, self.day, self.field)
class Meta:
unique_together = (('date', 'field'))
Could you please help?
Thanks.
Instead of overriding the save method you could use a post_save signal.
The basic usage is explained here: http://www.djangofoo.com/85/signal-connect-disconnect-django-signals.
So every time after a member is saved you could call a function that creates an appropriate reservation.
Tweaking the example ...
def create_reservation(sender, **kwargs):
# the object which is saved can be accessed via kwargs 'instance' key.
member = kwargs['instance']
print 'the object is now saved.'
# ...create reservation...
reservation = Reservation()
reservation.is_member = True
reservation.membership = member
....
reservation.save()

How does one do a model query from other model function?

I have the following classes: Student, LabJournal, JournalResponse, and JournalField. I want to define a "status" function for student to determine how many questions (JournalField) they've answered (JournalResponse). The problem is the function dies without a return on the following line:
total_questions = models.JournalResponse.objects.filter(owner__exact=self.id).filter(field__journal__exact=self.assignment).count()
My guess is that I'm doing the model query wrong from within the class definition, or that you're not allowed to query from within a separate model. However I have not found anything in the docs to confirm or deny this, and without any errors it's difficult to debug. Running Django 1.1.
Code below:
class Student (models.Model):
user = models.ForeignKey(User, unique=True, null=False, related_name='student')
teacher = models.ForeignKey(User, null=False, related_name='students')
assignment = models.ForeignKey(LabJournal, blank=True, null=True, related_name='students')
def get_absolute_url(self):
return "/labjournal/student/%i/" % self.id
def status(self):
if self.assignment == None : return "unassigned"
percent_done = 0
total_questions = models.JournalResponse.objects.filter(owner__exact=self.id).filter(field__journal__exact=self.assignment).count()
answered_questions = models.JournalResponse.objects.filter(owner__exact=self.id).filter(field__journal__exact=self.assignment).filter(text!=None).count()
percent_done = (answered_questions/total_questions)*100
return '%d%% done' % percent_done
class JournalResponse (models.Model):
owner = models.ForeignKey(Student, null=False, related_name='responses')
field = models.ForeignKey(JournalField, null=False, related_name='responses')
text = models.TextField(null=True, blank=True)
file = models.URLField(null=True, blank=True)
class JournalField (models.Model):
TYPE_CHOICES = (
(u'HTML', u'HTML'),
(u'IF', u'ImageField'),
(u'TF', u'TextField'),
)
journal = models.ForeignKey(LabJournal, null=False, related_name='fields', help_text='Parent Journal')
ordinal = models.IntegerField(help_text='Field order')
type = models.CharField(null=False, max_length=64, choices=TYPE_CHOICES, help_text='Field type')
# Contains HTML content for HTML fields, contains the text marked "question" closest
# to and above the current field for picture and text entry fields
content = models.TextField(help_text='Should contain HTML content for HTML (question) fields or associated (previous question) HTML for ImageFields and TextFields.')
UPDATED
Here's the working status method:
def status(self):
if self.assignment == None : return "unassigned"
percent_done = 0
# sets up query, but doesn't actually hit database
response_set = self.responses.filter(owner=self).filter(field__journal=self.assignment)
# force float so divide returns float
# the two count statements are the only two actual hits on the database
total_questions = float(response_set.count())
answered_questions = float(response_set.exclude(text='').count())
percent_done = (answered_questions/total_questions)*100
return '%d%% done' % percent_done
It looks like you're referring to models.JournalResponse which shouldn't exist (AttributeError?) because in the class definition the same models name is referring to django.db.models
You would need to refer to it via the actual model object, so JournalResponse.objects.filter().
In your case, you have a reverse relationship to JournalResponse from Student so you can simply use self.journalresponse_set.filter() to access JournalResponse.objects.filter(student=self)
http://docs.djangoproject.com/en/dev/topics/db/queries/#following-relationships-backward
Try:
self.journalresponse_set.filter(field__journal=self.assignment)
Also, your next filter line would break as well on text!=None. Use exclude(text=None) syntax instead.

Django Model relationship refactor help?

I'm sorta having a django / db brainfart.
In words I'm trying to come up with a better way to represent Address objects in my project. I have 5 or 6 different models that have one or more associations to an Address (Billing / Shipping / etc) and I need to allow these addresses to be created/modified/deleted. My first thought was to use the Admin for this since it seems like a natural fit.
However, I can't seem to figure out how to tell the Admin to restrict the visible set of addresses to the specific model in question(Account/Partner/Invoice). I found a really nasty, not viable, and incredibility horrible to maintain way to do it, which I will show below.
How can I do this efficiently (preferably in the Admin)? I am open to going to a m2m relationship which feels much more natural to me, and using a custom view/form, but I wanted to see if I was missing some Admin trick before I went that route. If I go that route I'm thinking I will need to use a GenericRelation so that I don't end up with a ton of lookup tables (one for each different entity).
EDIT: The same address may be used for different models, and for different instances of a particular model, BUT if an address is reused we must track who is using what to maintain independence between models and/or instances. Or in other words in a m2m relationship we can track who is using what with the intermediate table. If we aren't using a lookup table then we need to always copy the Address instance so that both objects have their own copy. (If there is an edit we need to make sure we aren't changing the preexisting relationships of anyone else. In other words edits are actually creates and reassigns in the m2m case.)
Here is an example that should pretty much work as an empty project that sorta shows how I want the address(es) isolated during add/edit/delete, but it also shows how horrible the solution is.
models.py
from django.db import models
class Account(models.Model):
name = models.CharField(max_length=200,blank=True)
#...
def __unicode__(self):
return u"%s - (%s)" %(self.name, self.address_set.all())
class Partner(models.Model):
name = models.CharField(max_length=200,blank=True)
discount = models.DecimalField(max_digits= 3, decimal_places=1, default=0)
#...
def __unicode__(self):
return u"%s - (%s)" %(self.name, self.address_set.all())
class Invoice(models.Model):
invoice_number = models.IntegerField(default=1)
#...
def __unicode__(self):
return u"%s - (%s)" %(self.invoice_number, self.address_set.all())
class Address(models.Model):
street = models.CharField(max_length=200,blank=True)
zip = models.CharField(max_length=10, verbose_name='Zip Code')
account = models.ForeignKey(Account, blank=True, null=True)
partner = models.ForeignKey(Partner, blank=True, null=True)
invoice = models.ForeignKey(Invoice, blank=True, null=True)
type = models.CharField(max_length=25, choices=(('B','Billing'),('S','Shipping')))
class Meta:
unique_together = (('type', 'account' ),
('type', 'partner' ),
('type', 'invoice' ),)
def __unicode__(self):
return "(%s) - %s %s" %(self.get_type_display(), self.street, self.zip)
admin.py
from django.contrib import admin
from relationships.rels.models import Partner, Account, Address, Invoice
class AcctAddrInline(admin.TabularInline):
model = Address
extra = 1
max_num =3
exclude = ('partner', 'invoice')
class PartAddrInline(admin.TabularInline):
model = Address
extra = 1
max_num =3
exclude = ('account', 'invoice')
class InvAddrInline(admin.TabularInline):
model = Address
extra = 1
max_num =2
exclude = ('account', 'partner')
class AccountAdmin(admin.ModelAdmin):
inlines = [AcctAddrInline,]
class PartnerAdmin(admin.ModelAdmin):
inlines = [PartAddrInline,]
class InvoiceAdmin(admin.ModelAdmin):
inlines = [InvAddrInline,]
admin.site.register(Invoice, InvoiceAdmin)
admin.site.register(Partner, PartnerAdmin)
admin.site.register(Account, AccountAdmin)
admin.site.register(Address)
EDIT: The same address may be used for different models, and for different instances of a particular model, BUT if an address is reused we must track who is using what to maintain independence between models and/or instances
It looks like you want to use COW pattern for addresses, but I don't think it plays nice with the whole idea of database integrity.
If you just want to separate account addresses from invoice addresses I'd suggest to use Multi-table model inheritance. This way you will have several sets of addresses while being able to browse all addresses at once.
Here's an example.
models.py
from django.db import models
class Account(models.Model):
name = models.CharField(max_length=200, blank=True)
def __unicode__(self):
return u"%s - (%s)" % (self.name, self.address_set.all())
class Partner(models.Model):
name = models.CharField(max_length=200, blank=True)
discount = models.DecimalField(max_digits= 3, decimal_places=1, default=0)
def __unicode__(self):
return u"%s - (%s)" % (self.name, self.address_set.all())
class Invoice(models.Model):
invoice_number = models.IntegerField(default=1)
def __unicode__(self):
return u"%s - (%s)" % (self.invoice_number, self.address_set.all())
class Address(models.Model):
street = models.CharField(max_length=200, blank=True)
zip = models.CharField(max_length=10, verbose_name='Zip Code')
def __unicode__(self):
return "%s %s" % (self.street, self.zip)
class AccountAddress(Address):
account = models.ForeignKey(Account, related_name='address_set')
class InvoiceAddress(Address):
invoice = models.ForeignKey(Invoice, related_name='address_set')
class PartnerAddress(Address):
partner = models.ForeignKey(Partner, related_name='address_set')
admin.py
from django.contrib import admin
# Wildcard import used for brevity
from relationships.rels.models *
class AccountAddressInline(admin.TabularInline):
model = AccountAddress
extra = 1
max_num = 3
class PartnerAddressInline(admin.TabularInline):
model = PartnerAddress
extra = 1
max_num = 3
class InvoiceAddressInline(admin.TabularInline):
model = InvoiceAddress
extra = 1
max_num = 3
class AccountAdmin(admin.ModelAdmin):
inlines = [AccountAddressInline,]
class PartnerAdmin(admin.ModelAdmin):
inlines = [PartnerAddressInline,]
class InvoiceAdmin(admin.ModelAdmin):
inlines = [InvoiceAddressInline,]
admin.site.register(Account, AccountAdmin)
admin.site.register(Partner, PartnerAdmin)
admin.site.register(Invoice, InvoiceAdmin)
admin.site.register(AccountAddress)
admin.site.register(InvoiceAddress)
admin.site.register(PartnerAddress)
# Uncomment if you want to browse all addresses available at once
# admin.site.register(PartnerAddress)
Note the related_name='address_set' hack. I don't know why, but it is the only way inline editing works when using foreign key from an inherited model. It seems that it's a bug in Django similar to (but rather with a reversed use-case) #11120 and #11121.
I, personally, would put foreign keys to your Address model on Account, Partner and Invoice instead of having Address aware of what it is the address to. That, MAY, solve your problem.