Django model is saving twice - python

I'm trying to save Authors model, but it keeps saving twice into the database.
views.py
........................
for book_data in data['items']:
volume_info = book_data['volumeInfo']
title = volume_info['title']
genres = volume_info.get('categories')
authors = volume_info.get('authors')
description = volume_info.get('description')
if not Books.objects.filter(title=title).exists():
book = Books.objects.create(title=title, description=description)
# Does authors exists in database?
existing_authors = Authors.objects.filter(author_name__in=authors)
existing_authors_names = {authors.author_name for authors in existing_authors}
# Create a list of missing authors
missing_authors = [
Authors(author_name=author_name)
for author_name in authors
if author_name not in existing_authors_names
]
# Creating author before adding it to relation
if missing_authors:
missing_authors = Authors.objects.bulk_create(missing_authors)
print(missing_authors)
for m in missing_authors:
m.save()
# Adding to relation
book.authors.add(*existing_authors, *missing_authors)
..........................
I think the problem is in for m in missing_authors right?
models.py
class Authors(models.Model):
author_name = models.CharField(max_length=200)
def __str__(self):
return self.author_name
class Books(models.Model):
title = models.CharField(max_length=300)
description = models.TextField(blank=True, null=True)
authors = models.ManyToManyField(Authors, blank=True)
genres = models.ManyToManyField(Genres, blank=True)
def __str__(self):
return self.title
database is sqllite3
Django version is 2.2.1

The bulk_create method automatically saves the results after the query is executed.
Change your code to this:
if missing_authors:
Authors.objects.bulk_create(missing_authors)
''' remove these lines
for m in missing_authors:
m.save()
#not sure what this line is doing exactly, but it might be causing your problem
book.authors.add(*existing_authors, *missing_authors)
'''
Update
If you can set unique=True for your author_name column, then try the following:
class Authors(models.Model):
author_name = models.CharField(max_length=200, unique=True)
Authors.objects.bulk_create(missing_authors, ignore_conflicts=True)
for m in missing_authors:
m.save()
book.authors.add(*existing_authors, *missing_authors)

Related

Checking if something exists and adding or getting an object from django

I'm scrapping a shop from Woocommerce, and i'd like to take the products brand name, and compare if there's already name like this in the database and if so put the id of it in our base and if not, create an instance of a productProducer
for page in range (99, 100):
parameters = {'per_page': 1, 'page': f'{page}', 'stock_status': 'instock', 'status': 'publish'}
products = wcapi.get("products", params=parameters).json()
for product in products:
print(product)
name = product['name']
slug = product['slug']
description = product['description']
id = product['id']
price = product['price']
link = product['permalink']
for brands in product['images']:
brands_name = brands['name']
for image in product['images']:
src = image['src']
print(product['categories'])
parsed_categories = []
for category in product['categories']:
parsed_categories.append(dir[str(category['id'])])
print(parsed_categories)
db_product = Product.objects.create(name=name, slug=slug, product_producer_id=1, unique_id=int(id),
description=description)
db_product.product_category.set(parsed_categories)
ProductInStore.objects.create(product=db_product, store_id=1, price=price, currency='PLN', url=link)
ProductImage.objects.create(product=db_product, url=src)
I have a dir with mapping keys and values for parsing categories outside.
Models look like this:
class ProductImage(models.Model):
product = models.ForeignKey('Product', on_delete=models.CASCADE)
filename = models.FileField(upload_to='ecommerce_product_img/', null=True, blank=True)
url = models.CharField(max_length=200, null=True,blank=True)
class ProductProducer(models.Model):
name = models.CharField(max_length=200, unique=True)
slug = models.SlugField(max_length=200, unique=True, null=False, editable=False)
created_at = models.DateTimeField(editable=False, default=timezone.now)
updated_at = models.DateTimeField(default=timezone.now)
Id like to check if brands_name already exists in productProducer_name and if not, create a new instance of ProductProducer, then also i'd like to add this new(or current) productproducer in db_product
You can use Django's get_or_create for this purpose.
producer, is_created = ProductProducer.objects.get_or_create(name=name, defaults={'slug': slug})
If there was already an object with that name, it is fetched from the DB and stored int he variable producer, and is_created is False. If there was not yet an object with that name, one is created with the given slug, stored in the variable producer, and is_created is True.

Django ForeignKey accept two models

I'm working on this big project with Django and I have to update the database. I have to add another table which will replace another later.
So I want to add in a model the possibility to have a field where I can have either the old model OR the new one.
Here is the code of the old model:
class Harvests(models.Model):
ident_culture = models.IntegerField(primary_key=True)
intitule_culture = models.CharField(max_length=50, blank=True)
nom_fertiweb = models.CharField(max_length=50, blank=True, null = True)
affichage_quintaux_tonne = models.CharField(max_length=1,
choices=RENDEMENT_CHOICES, default = 'T')
type_culture = models.ForeignKey("TypeCulture", null=True)
slug = models.SlugField(null=True, blank=True)
image = models.ImageField(upload_to = 'images_doc_culture/',
null=True, blank = True)
affichage = models.BooleanField(default = True)
class Meta:
verbose_name = "Liste - Culture"
verbose_name_plural = "Liste - Cultures"
ordering = ['intitule_culture']
def __str__(self):
return self.intitule_culture
def label(self):
return self.intitule_culture or ''
#classmethod
def get_choices(cls):
choices = [('', corp.EMPTY_CHOICE_LBL)]
c_category_lbl, c_category = '', []
for item in cls.objects.all():
choices.append((item.pk, item.intitule_culture))
return choices
And there is the code od the new one I created:
class Crops(models.Model):
intitule_culture = models.CharField(max_length=75, blank=True)
affichage_quintaux_tonne = models.CharField(max_length=2,
choices=RENDEMENT_CHOICES, default = 'T')
type_culture = models.ForeignKey("TypeCulture", null=True)
ident_culture = models.IntegerField(primary_key=True)
affichage = models.BooleanField(default = True)
id_marle = models.IntegerField(null=True)
class Meta:
verbose_name = "Liste - Culture 2019"
verbose_name_plural = "Liste - Cultures 2019"
ordering = ['intitule_culture']
def __str__(self):
return self.intitule_culture
def label(self):
return self.intitule_culture or ''
#classmethod
def get_choices(cls):
choices = [('', corp.EMPTY_CHOICE_LBL)]
c_category_lbl, c_category = '', []
for item in cls.objects.all():
choices.append((item.pk, item.intitule_culture))
return choices
I want to accept both models in the field culture in this model:
class CompanyHarvest(models.Model):
company = models.ForeignKey('corp.Company', verbose_name='Exploitation',
related_name ='cultures')
culture = models.ForeignKey(Harvests, verbose_name ='Culture')
precision = models.CharField(max_length=255, blank=True)
saison_culture = models.CharField(max_length=1, choices=SAISON_CHOICES,
default = 'P')
class Meta:
verbose_name = "Expl. - Culture"
verbose_name_plural = "Expl. - Cultures"
unique_together = ('company', 'culture', 'precision', 'saison_culture')
def __str__(self):
return str(self.culture) + ' ' + self.precision + \
' ' + str(self.get_saison_culture_display() )
#property
def slug(self):
return "_".join([slugify(str(self.culture or '')),
slugify(str(self.precision or ''))]
)
I'm new to Django, can anyone help me with this please ? (^-^)
This is not possible - at least not this way. And this is not a Django limitation but a SQL one, a foreign key cannot reference either one table or another.
A possible and simple obvious solution here would be to have two foreign keys in CompanyHarvest - one for each of the old and new model -, each with blank=True et default=None, but it can quickly make a mess of all the client code (all code using CompanyHarvest).
Much better solutions would be to either only keep the existing model (adding any new field/feature to it and eventually hiding obsolete ones) or migrate all old model records to the new model (this can be combined with the naive "two foreign keys" solution so you can keep the old table and records as archives if necessary).
Also - totally unrelated but -, this:
#classmethod
def get_choices(cls):
choices = [('', corp.EMPTY_CHOICE_LBL)]
c_category_lbl, c_category = '', []
for item in cls.objects.all():
choices.append((item.pk, item.intitule_culture))
return choices
1/ should be defined on the manager (cf https://docs.djangoproject.com/en/2.1/topics/db/managers/#adding-extra-manager-methods)
2/ should be written using .values() queryset (which will save on both the db query and building full-blown instances for no good reason):
for item in cls.objects.values("pk", "intitule_culture"):
choices.append(item)
3/ and could very possibly (i'd have to see how it's used) replaced by a ModelChoiceField in the calling code.
Oh and yes: if you allow blanks for text fields, you very probably want to force the empty string as default so you don't two possible (and incompatible) cases (sql NULL and the empty string) when no value is given.

Django JOIN not Working

My model is like this
class NounPlural(models.Model):
idnoun_plural = models.IntegerField(db_column='idNoun_Plural', primary_key=True) # Field name made lowercase.
nns = models.TextField(db_column='NNS', blank=True) # Field name made lowercase.
news_idnews = models.ForeignKey(News, db_column='news_idnews')
class News(models.Model):
idnews = models.IntegerField(primary_key=True)
source = models.TextField(blank=True)
title = models.TextField(blank=True)
My view.py is like this
def allobj(request):
obj_json = serializers.serialize('json', NounPlural.objects.select_related('news_idnews')[:5] )
obj_list = json.loads( obj_json )
json_data = json.dumps( obj_list )
return HttpResponse( json_data, content_type='application/json' )
All I want to do is to get the NounPlural and also the title from News.When I go to allobj link I only get NounPlural.
In the django documentation related to your question, I always see the need for a .get(id=<something>) with the select_related() call, like so:
Book.objects.select_related('person__city').get(id=4)
So shouldn't your call be:
NounPlural.objects.select_related('news_idnews').get(id=<Newsid>)[:5]
Is this the reason you only have a NounPlural and not the QuerySet you want ?

How to connect data between author and book table which has ManyToMany relation in Django

I am learning Django and have been complete Model's chapter by "The Django Book" but didn't get the way to insert value in ManyToMany and ForeignKey for below question:
In this below model, How do i insert detail of "Author" and "Publisher" for book name = "The book" with publication-date = "28/10/2013"
And after inserting value, how do i get back "Author" and "Publisher" for book name = "The book"
class Author(models.Model):
first_name = models.CharField(max_length = 20)
last_name = models.CharField(max_length = 20, blank = True)
email = models.EmailField(blank = True)
class Publisher(models.Model):
name = models.CharField(max_length=30)
address = models.CharField(max_length=50)
city = models.CharField(max_length=60)
state_province = models.CharField(max_length=30)
country = models.CharField(max_length=50)
website = models.URLField()
class Book(models.Model):
title = models.CharField(max_length=100)
publisher = models.ForeignKey(Publisher)
authors = models.ManyToManyField(Author)
publication_date = models.DateField()
if book is already there in database.
just simply run like
book_obj = Book.objects.get(title="Test Title")
author1 = Author.objects.get(name="test author1")
author2 = Author.objects.get(name="test author2")
author3 = Author.objects.get(name="test author3")
book_obj.authors.add(author1, author2, author3)
Same you can add Publisher if book is already in database.
If you want to create new entry of book.
publisher = Publisher.objects.get(name='Test publisher')
author = Author.objects.get(name='Test author')
Book.objects.create(title='Title1', publication_date='28/10/2013', authors=author, publisher=publisher)
Let's say you want to add a Stephen King's book Pet Semetary. You need to have the publisher and author instances already before adding the book object. Then it's very simple:
publisher = Publisher.objects.get(name='Simon & Schuster')
author = Author.objects.get(name='Stephen King')
new_book = Book(title='Pet Semetary',
publication_date='28/10/2013',
authors=author,
publisher=publisher)
new_book.save()
Is this the solution you were looking for?
update on comment
If the book object exists you can just apply it like so:
# create Author and Publisher:
new_author = Author(first_name='Stephen', last_name='King')
new_author.save()
new_publisher = Publisher(...bla bla...)
new_publisher.save()
# link them to your already existing book instance
book_instance = Book.objects.get(title='Pet Semetary')
book_instance.author = new_author
book_instance.publisher = new_publisher
Of course, you should use less general names than new_author or book_instance but you get the idea

Specific Quote Issue

Here is my problem. I have a model Project, that has a quote field in it. When a new instance of project is created I need to append the last 2 digits of the year plus a hyphen onto the start of the "quote" field. Ex. 2010 = "10-". Im just not quite sure how to start it?
As of right now I have hard coded in "10-" in as a pre-quote field, but I do not want to have to do that.
models.py
class Project(models.Model):
client = models.ForeignKey(Clients, related_name='projects')
created_by = models.ForeignKey(User, related_name='created_by')
#general information
proj_name = models.CharField(max_length=255, verbose_name='Project Name')
pre_quote = models.CharField(max_length=3,default='10-')
quote = models.IntegerField(max_length=10, verbose_name='Quote #', unique=True)
desc = models.TextField(verbose_name='Description')
starts_on = models.DateField(verbose_name='Start Date')
completed_on = models.DateField(verbose_name='Finished On')
Anyone have to do this before? Or have any suggestions?
Try this:
def save(self):
today = datetime.date.today()
self.quote = "%s-%s" % (str(today.year)[2:4], self.quote)
Assuming you imported datetime.
Your existing quote field is set as an integer. You will need to set this as a text field. Once you do that, you can override the save() function to prepend "10-" to the field.
class Project(models.Model):
client = models.ForeignKey(Clients, related_name='projects')
created_by = models.ForeignKey(User, related_name='created_by')
proj_name = models.CharField(max_length=255, verbose_name='Project Name')
quote = models.TextField(max_length=10, verbose_name='Quote #', unique=True)
desc = models.TextField(verbose_name='Description')
starts_on = models.DateField(verbose_name='Start Date')
completed_on = models.DateField(verbose_name='Finished On')
def save(self):
self.quote = "10-" + self.quote

Categories