I am struggling to populate models in Django by using ForeignKey. Let's say we have as in import_export documentation the following example:
class Author(models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Category(models.Model):
id = models.BigAutoField(primary_key=True)
name = models.CharField(max_length=100)
def __str__(self):
return self.name
class Book(models.Model):
name = models.CharField('Book name', max_length=100)
author = models.ForeignKey(Author, blank=True, null=True, )
...
price = models.DecimalField(max_digits=10, decimal_places=2, null=True, blank=True)
categories = models.ManyToManyField(Category, blank=True)
def __str__(self):
return self.name
How can I implement import_export module that can check if there is an existing author by name (not by id), that is not case sensitive, and that can generate a new author if it does not exist?
As an example, let's say the CSV file looks like:
name,author,...,price,categories
J.R.R. Tolkien,Lord of the Rings,...,40,["cat1","cat2"]
Also, if there is a DateTime field, how to generate that in ForeignKey table?
NOTE: I know about use of natural key:
from import_export.fields import Field
from import_export.widgets import ForeignKeyWidget
class AuthorManager(models.Manager):
def get_by_natural_key(self, name):
return self.get(name=name)
class Author(models.Model):
objects = AuthorManager()
name = models.CharField(max_length=100)
birthday = models.DateTimeField(auto_now_add=True)
def natural_key(self):
return (self.name,)
# Only the author field uses natural foreign keys.
class BookResource(resources.ModelResource):
author = Field(
column_name = "author",
attribute = "author",
widget = ForeignKeyWidget(Author, use_natural_foreign_keys=True)
)
class Meta:
model = Book
But I am not sure how to check for UPPER or lower case in the CSV. And how to generate a new Author if it does not exist.
There are a couple of ways of creating an FK relation during import if it does not already exist.
Option 1 - override the before_import_row() method
class BookResource(resources.ModelResource):
# note use of 'iexact' for case-insensitive lookup
def before_import_row(self, row, **kwargs):
author_name = row["author"]
Author.objects.get_or_create(name__iexact=author_name,
defaults={"name": author_name})
# other code omitted
Option 2 - subclass ForeignKeyWidget
Simply subclass ForeignKeyWidget and implement the check in clean():
class AuthorForeignKeyWidget(widgets.ForeignKeyWidget):
def clean(self, value, row=None, **kwargs):
author, created = Author.objects.get_or_create(name__iexact=value,
defaults={"name": value})
return author
class BookResource(resources.ModelResource):
author = fields.Field(
column_name='author',
attribute='author',
widget=AuthorForeignKeyWidget(Author))
# other code omitted
Either way will work fine. I would personally use option 2.
Also, if there is a DateTime field, how to generate that in ForeignKey table?
Since you are calling Author.objects.get_or_create() you can add a date if you wish, for example:
author, created = Author.objects.get_or_create(name__iexact=value,
defaults={"name": value, "created": timezone.now()})
If using natural keys you can adjust the code as desired.
Related answer about creating in bulk mode
Related
I want to make the string representation of a field show data based on a JOIN, for instance:
For Penciler - I want the string representation to resolve to
John Doe (DC) - But the publisher value in that class is a Foreign Key - How do I reference the publisherName?
from django.db import models
class Series(models.Model):
seriesId = models.AutoField(primary_key=True)
series_name = models.CharField(max_length=300)
publisher = models.ForeignKey('Publisher', on_delete = models.PROTECT)
first_published = models.DateField()
last_published = models.DateField()
discontinued = models.BooleanField(default=False)
def __str__(self):
return f'{self.series_name} - {self.publisher} ({self.first_published - self.last_published})'
class Meta:
ordering = ['publication_year','title']
class Publisher(models.Model):
publisherId = models.AutoField(primary_key=True)
publisherName = models.CharField(max_length=250, null=False)
def __self__(self):
return self.publisherName
class Penciler(models.Model):
pencilerID = models.AutoField(primary_key=True)
pencilerName = models.CharField(max_length=200)
publisher = models.ForeignKey('Publisher', on_delete= models.PROTECT)
def __str__(self):
return self.pencilerName (self.publisher)
You can access the related Publisher instance through the ForeignKey field and get the publisherName in the __str__() method so:
def __str__(self):
publisher_name = self.publisher.publisherName
return f'{self.pencilerName} ({publisher_name})'
Additionally, I'd recommend you to use string formatting, such as using f-strings.
It is as simple as that:
def __str__(self):
return f"{self.pencilerName} ({self.publisher})"
Are there exists any examples of Django widgets which can be useful for ManyToManyFields with 'through' attributes? For example, i have these models (got the source from django documentation):
from django.db import models
class Person(models.Model):
name = models.CharField(max_length=128)
def __str__(self): # __unicode__ on Python 2
return self.name
class Group(models.Model):
name = models.CharField(max_length=128)
members = models.ManyToManyField(Person, through='Membership')
def __str__(self): # __unicode__ on Python 2
return self.name
class Membership(models.Model):
person = models.ForeignKey(Person, on_delete=models.CASCADE)
group = models.ForeignKey(Group, on_delete=models.CASCADE)
date_joined = models.DateField()
invite_reason = models.CharField(max_length=64)
Obvisously, standart ModelMultipleChoiceField won't work here. I need to populate 'date_joined' , and 'invite_reason' while adding. Which is the simpliest way to achieve this?
This is a bit too complex for a simple widget. I can't even imagine how it would look like. You will have to use inline formsets for that purpose.
This should give something like this:
from django.forms import inlineformset_factory
MembershipFormSet = inlineformset_factory(Group, Membership, fields=(
'person', 'date_joined', 'invite_reason'))
group = Group.objects.get(pk=group_id)
formset = MembershipFormSet(instance=group)
Within django.contrib.admin, you can use inlines with InlineModelAdmin.
I'm trying to create a slug field in django that requires the use of 2 table fields from 2 different tables.
What is the best way of going about this?
For example:
If I had a Restaurant table with a restaurant_name field and a Location table with a location_name field, how do I create a slug field from these two values? That is, a slugfield generated from restaurant_name + location_name
EDIT:
If possible, I would like to use AutoSlug Field in the solution. For example here is an implementation with a single field to slugify:
class Cuisine(models.Model):
name = models.CharField(max_length=100)
name_slug = AutoSlugField(populate_from='name', unique=True, null=True)
def __str__(self):
return self.name
This is simple, you don't really have to write some special handler for you AutoSlugField.
from django.utils.text import slugify
class Restaurant(models.Model):
name = models.CharField(max_length=100)
location = models.ForeignKey(Location)
name_slug = models.AutoSlugField(populate_from='name', unique=True, null=True)
def save(self, *args, **kwargs):
'''
Assuming that you don't have a slug field on your other model
This will simply add the extra slug to your restaurant name.
Personally, i would also add a slug field to the location table so you
simply call location.slug and don't have to slugify()
'''
self.name_slug += slugify(self.location.name)
super(Restaurant, self).save(*args, **kwargs)
One thing to keep in mind here is that the django-autoslug doesn't mention anything about special max_length to their AutoSlugField, there will probably be problems with max_length for this field.
So an alternative solution is way simpler than the above:
from django.utils.text import slugify
class Restaurant(models.Model):
name = models.CharField(max_length=100)
location = models.ForeignKey(Location)
# sum both fields max_length for the slug
name_slug = models.SlugField(max_length=200, unique=True, null=True)
def save(self, *args, **kwargs):
# just check if name or location.name has changed
self.name_slug = '-'.join((slugify(self.name), slugify(self.location.name)))
super(Restaurant, self).save(*args, **kwargs)
p.s: you can make an edit with the right model names.
models.py
class Country(models.Model):
code = models.CharField(max_length=2, unique=True)
name = models.CharField(max_length=100)
def __unicode__(self):
return self.name
class Meta:
verbose_name_plural = 'countries'
class State(models.Model):
country = models.ForeignKey(Country)
code = models.CharField(max_length=5)
name = models.CharField(max_length=40)
def __unicode__(self):
return self.name
I would like to be able to do something like this:
state, created = State.objects.get_or_create(name='myState',code='myCode',
country__code='myCountryCode',
country__name='myCountryName')
Now, my solution (not actually tried yet):
class StateManager(models.Manager):
def get_or_create(self, **kwargs):
country_data = {}
for key, value in kwargs.iteritems():
if key.startswith('country__'):
country_data[key.replace('country__', '')] = value
#will this work?
country, created = Country.objects.get_or_create(country_data)
#get_or_create needs to be called here excluding 'country__' arguments
#and adding the 'country' object
super(StateManager, self).get_or_create(modified_kwargs)
I would like if there is a better way of doing this in Django 1.6 before trying to make this code work.
Your solution will introduce a bunch of sources of error/exception. Why not just follow the standard procedure?
country, created = Country.objects.get_or_create(name='myCountryName', code='myCountryCode')
state, created = State.objects.get_or_create(country=country, name='myStateName', code='myStateCode')
i am working with django to create three tables that define a store, it's item list and a price list. In the 'price_list' model i created Foreign key links to the other two tables that i later use to create a compisite primary key using the unique together option. It all validates without an error but when i try to change the 'price_list' via the admin interface, i get the error as stated above in the title to this post. Editing for the other two models goes on without a glitch.
As i understand it, the error is saying the foreign key linked to the primary key of the businesses table is null. How can that be when the value is automatically generated?
Please help
models.py
from django.db import models
class Businesses(models.Model):
business_name = models.CharField(max_length=50, unique=True)
telephone_number = models.CharField(max_length=15)
where = models.ManyToManyField('Location', verbose_name='where?')
def __unicode__(self):
return self.business_name
class Meta:
ordering = ['business_name']
class Location(models.Model):
located_at = models.CharField(max_length=30)
city = models.CharField(max_length=30)
country = models.CharField(max_length=30)
def __unicode__(self):
return u'%s' % (self.located_at)
class Meta:
ordering = ['located_at']
class Item_list(models.Model):
item_name = models.CharField(max_length=50, unique=True)
def __unicode__(self):
return self.item_name
class Meta:
ordering = ['item_name']
class Price_list(models.Model):
price_list_id = models.AutoField(primary_key=True)
price = models.IntegerField(max_length=50)
business_id = models.ForeignKey(Businesses, related_name='businessID')
item_id = models.ForeignKey(Item_list, related_name='ItemID')
business_name = models.ForeignKey(Businesses, to_field='business_name', related_name='businessName')
item_name = models.ForeignKey(Item_list, to_field='item_name', related_name='itemName')
def __unicode__(self):
return u'%s' % self.price
class Meta:
ordering = ['price']
unique_together = (("price_list_id", "business_id", "item_id"),)
admin.py
from django.contrib import admin
from biaSearch.app.models import *
class BusinessAdmin(admin.ModelAdmin):
list_display = ('business_name', 'telephone_number')
search_field = ('business_name')
filter_horizontal = ('where',)
class LocationAdmin(admin.ModelAdmin):
list_display = ('located_at', 'city', 'country')
search_field = ('located_at')
list_filter = ('located_at', 'city')
class Item_listAdmin(admin.ModelAdmin):
list_display = ('item_name',)
class Price_listAdmin(admin.ModelAdmin):
list_display = ('price',)
fields = ('price', 'business_name', 'item_name')
admin.site.register(Businesses, BusinessAdmin)
admin.site.register(Location, LocationAdmin)
admin.site.register(Item_list, Item_listAdmin)
admin.site.register(Price_list, Price_listAdmin)
I suspect you're missing a capital B in the foreign key definition for "businessID" on Price_List.
I'm not sure but I think the whole "related_name='businessID'" argument is unnecessary since you're wanting to create a foreign key to the primary key of the Businesses table.
Try it - it might work!