Django-import-export update_or_create with UniqueConstraint - django

I have this Profile model together with a constraint similar to a unique_together:
class Profile(models.Model):
#Personal Information
firstname = models.CharField(max_length=200)
lastname = models.CharField(max_length=200, blank=True, null=True)
email = models.EmailField(max_length=200)
investor_type = models.CharField(max_length=200, choices=investor_type_choices)
class Meta:
constraints = [
models.UniqueConstraint(fields=['email', 'investor_type'], name='email and investor_type')
]
I want to implement a function update_or_create on the Profile which uses the email and investor_type as the argument for searching for the object.
I tried adding this to my ProfileResource:
def before_import_row(self, row, row_number=None, **kwargs):
try:
self.email = row["email"]
except Exception as e:
self.email = None
try:
self.investor_type = row["investor_type"]
except Exception as e:
self.investor_type = None
def after_import_instance(self, instance, new, row_number=None, **kwargs):
try:
# print(self.isEmailValid(self.email), file=sys.stderr)
if self.email and self.investor_type:
profile, created = Profile.objects.update_or_create(
email=self.email,
investor_type=self.investor_type,
defaults={
'firstname': 'helloo',
'lastname': 'wooorld',
})
except Exception as e:
print(e, file=sys.stderr)
but adding a non-existing Profile object:
through django-import-export:
is already giving out an error, Profile with this Email and Investor type already exists despite it not existing in the first place.

Related

how to pass the Integrity error django and DRF without stoping execution raising the error

I am byulding an API using django and DRF my code is this
models.py
class Company(models.Model):
"""Company object."""
name_company = models.CharField(max_length=255)
symbol = models.CharField(max_length=10, unique=True)
cik = models.CharField(max_length=150, blank=True)
sector = models.CharField(max_length=150, blank=True)
industry_category = models.CharField(max_length=150, blank=True)
company_url = models.TextField(blank=True)
description = models.TextField(blank=True)
def __str__(self):
return self.name_company
views.py
class CompanyViewSet(viewsets.ModelViewSet):
"""View for manage company APIs."""
serializer_class = serializers.CompanyDetailSerializer
queryset = Company.objects.all()
authentication_classes = [TokenAuthentication]
permission_classes = [IsAuthenticated]
def get_serializer_class(self):
"""Return the serializer class for request."""
if self.action == 'list':
return serializers.CompanySerializer
return self.serializer_class
def perform_create(self, serializer):
"""Create a new Company."""
try:
serializer.save()
except IntegrityError:
print('Symbol exists already.')
pass
serializers.py
class CompanySerializer(serializers.ModelSerializer):
"""Serializer for Company."""
class Meta:
model = Company
fields = [
'id', 'name_company', 'symbol', 'cik', 'sector',
'industry_category', 'company_url',
]
read_only_fields = ['id']
def create(self, validated_data):
try:
instance, created = Company.objects.get_or_create(**validated_data)
if created:
return instance
except IntegrityError:
pass
class CompanyDetailSerializer(CompanySerializer):
"""Serializer for Company details."""
class Meta(CompanySerializer.Meta):
fields = CompanySerializer.Meta.fields + ['description']
And right now i am doing unit tests using in this file.
test_company.py
def create_company(**params):
"""Create and return a sample company."""
defaults = {
'name_company': 'Apple',
'symbol': 'AAPL',
'cik': '0000320193',
'sector': 'Technology',
'industry_category': 'Consumer Electronics',
'company_url': 'https://www.apple.com/',
'description':'',
}
defaults.update(params)
company = Company.objects.create(**defaults)
return company
def test_retrieve_companies(self):
"""Test retrieving a list of Companies."""
create_company()
create_company()
create_company(
name_company='Tesla',
symbol='TSLA',
)
res = self.client.get(COMPANIES_URL)
companies = Company.objects.all().order_by('id')
serializer = CompanySerializer(companies, many=True)
self.assertEqual(res.status_code, status.HTTP_200_OK)
self.assertEqual(res.data, serializer.data)
I am getting IntegrityError, what i want is that the run time continue without stopping execution raising the error that is why i am testing it inserting APPLE twice.
I am trying to catch the error with this code in the views.py but does not catch it.
def perform_create(self, serializer):
"""Create a new Company."""
try:
serializer.save()
except IntegrityError:
print('Symbol exists already.')
pass
my error is this:
django.db.utils.IntegrityError: duplicate key value violates unique constraint "core_company_symbol_50a489f1_uniq"
DETAIL: Key (symbol)=(AAPL) already exists.
Thank you in advance.

Django-taggit how to modify to have same tag added by different user

I am trying to modify django-taggit to allow the same tag to be added by separate teams.
I have modified django-taggit Model so that it has user and team_id values added when a user adds a new tag to taggig_tag table. This also adds user and team_id values to taggit_taggeditems table.
The goal is to allow teams to edit or delete their own tags and that should not affect other teams, so different teams need to have their own separate sets of tags.
In my modified scenario, the tag name and team_id constitute a distinct tag. I expect i can test team_id or concatenate it to the tag name before django-taggit tests for distinct tags. But do not see where django-taggit does that.
Question: Where in the django-taggit code does it look for duplicate tag values?
`apps.py`
`forms.py`
`managers.py`
`models.py`
`utils.py`
`views.py`
My modified django-taggit Model code is below.
from django.contrib.contenttypes.fields import GenericForeignKey
from django.contrib.contenttypes.models import ContentType
from django.db import IntegrityError, models, router, transaction
from django.utils.text import slugify
from django.utils.translation import gettext, gettext_lazy as _
from django.conf import settings
### MODIFICATION: added django CRUM to get request user
from crum import get_current_user
try:
from unidecode import unidecode
except ImportError:
def unidecode(tag):
return tag
class TagBase(models.Model):
### MODIFICATION: added team and user to model, removed unique=True
name = models.CharField(verbose_name=_("Name"), max_length=100)
slug = models.SlugField(verbose_name=_("Slug"), max_length=100)
team_id = models.CharField(max_length=10, blank=False, null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True,
on_delete=models.DO_NOTHING)
def __str__(self):
return self.name
def __gt__(self, other):
return self.name.lower() > other.name.lower()
def __lt__(self, other):
return self.name.lower() < other.name.lower()
class Meta:
abstract = True
def save(self, *args, **kwargs):
### MODIFICATION: added team and user to taggit_taggeditem model
### get request user with django CRUM get_current_user()
self.user = get_current_user()
self.team_id = get_current_user().team_id
if self._state.adding and not self.slug:
self.slug = self.slugify(self.name)
using = kwargs.get("using") or router.db_for_write(
type(self), instance=self
)
# Make sure we write to the same db for all attempted writes,
# with a multi-master setup, theoretically we could try to
# write and rollback on different DBs
kwargs["using"] = using
# Be oportunistic and try to save the tag, this should work for
# most cases ;)
### MODIFICATION: remove IntegrityError try/except for unique
which is removed
#try:
with transaction.atomic(using=using):
res = super().save(*args, **kwargs)
return res
#except IntegrityError:
# pass
### MODIFICATION: remove slugs create as no longer checking for
duplicate slugs
# Now try to find existing slugs with similar names
#slugs = set(
# self.__class__._default_manager.filter(
# slug__startswith=self.slug
# ).values_list("slug", flat=True)
#)
i = 1
#while True:
# slug = self.slugify(self.name, i)
# if slug not in slugs:
# self.slug = slug
# # We purposely ignore concurrecny issues here for now.
# # (That is, till we found a nice solution...)
# return super().save(*args, **kwargs)
# i += 1
while True:
slug = self.slugify(self.name, i)
#if slug not in slugs:
self.slug = slug
# We purposely ignore concurrecny issues here for now.
# (That is, till we found a nice solution...)
return super().save(*args, **kwargs)
i += 1
else:
return super().save(*args, **kwargs)
def slugify(self, tag, i=None):
slug = slugify(unidecode(tag))
if i is not None:
slug += "_%d" % i
return slug
class Tag(TagBase):
class Meta:
verbose_name = _("Tag")
verbose_name_plural = _("Tags")
app_label = "taggit"
class ItemBase(models.Model):
def __str__(self):
return gettext("%(object)s tagged with %(tag)s") % {
"object": self.content_object,
"tag": self.tag,
}
class Meta:
abstract = True
#classmethod
def tag_model(cls):
field = cls._meta.get_field("tag")
return field.remote_field.model
#classmethod
def tag_relname(cls):
field = cls._meta.get_field("tag")
return field.remote_field.related_name
#classmethod
def lookup_kwargs(cls, instance):
return {"content_object": instance}
class TaggedItemBase(ItemBase):
tag = models.ForeignKey(
Tag, related_name="%(app_label)s_%(class)s_items",
on_delete=models.CASCADE
)
class Meta:
abstract = True
#classmethod
def tags_for(cls, model, instance=None, **extra_filters):
kwargs = extra_filters or {}
if instance is not None:
kwargs.update({"%s__content_object" % cls.tag_relname():
instance})
return cls.tag_model().objects.filter(**kwargs)
kwargs.update({"%s__content_object__isnull" % cls.tag_relname():
False})
return cls.tag_model().objects.filter(**kwargs).distinct()
class CommonGenericTaggedItemBase(ItemBase):
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
verbose_name=_("Content type"),
related_name="%(app_label)s_%(class)s_tagged_items",
)
content_object = GenericForeignKey()
### MODIFICATION: added team and user to taggit_taggeditem model
team_id = models.CharField(max_length=10, blank=False, null=False)
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True,
on_delete=models.DO_NOTHING)
class Meta:
abstract = True
#classmethod
def lookup_kwargs(cls, instance):
return {
"object_id": instance.pk,
"content_type": ContentType.objects.get_for_model(instance),
### MODIFICATION: added team and user to taggit_taggeditem model
"user": get_current_user(),
"team_id": get_current_user().team_id,
}
#classmethod
def tags_for(cls, model, instance=None, **extra_filters):
tag_relname = cls.tag_relname()
kwargs = {
"%s__content_type__app_label" % tag_relname:
model._meta.app_label,
"%s__content_type__model" % tag_relname: model._meta.model_name,
}
if instance is not None:
kwargs["%s__object_id" % tag_relname] = instance.pk
if extra_filters:
kwargs.update(extra_filters)
return cls.tag_model().objects.filter(**kwargs).distinct()
class GenericTaggedItemBase(CommonGenericTaggedItemBase):
object_id = models.IntegerField(verbose_name=_("Object id"),
db_index=True)
class Meta:
abstract = True
class GenericUUIDTaggedItemBase(CommonGenericTaggedItemBase):
object_id = models.UUIDField(verbose_name=_("Object id"), db_index=True)
class Meta:
abstract = True
class TaggedItem(GenericTaggedItemBase, TaggedItemBase):
class Meta:
verbose_name = _("Tagged Item")
verbose_name_plural = _("Tagged Items")
app_label = "taggit"
### MODIFICATION: added team_id and user to taggit_taggeditems table
constraints
index_together = [["content_type", "object_id", "team_id", "user"]]
unique_together = [["content_type", "object_id", "tag", "team_id",
"user"]]

Django form test fails

I'm trying to perform a simple test on my form to confirm that it's not valid when there is no data given and is valid when data is given.
When running tests with pytest (py.test) the test with no data works fine but I'm getting this error for the test with data present:
AssertionError: Should be valid if data is given
E assert False is True
E + where False = <bound method BaseForm.is_valid of <PostForm bound=True, valid=False, fields=(title;content;author;image;published;draft;category;read_time)>>()
E + where <bound method BaseForm.is_valid of <PostForm bound=True, valid=False, fields=(title;content;author;image;published;draft;category;read_time)>> = <PostForm bound=True, valid=False, fields=(title;content;author;image;published;draft;category;read_time)>.is_valid
posts/tests/test_forms.py:21: AssertionError
my models.py:
from django.db import models
from django.core.urlresolvers import reverse
from django.conf import settings
from django.db.models.signals import pre_save
from django.utils import timezone
from django.utils.text import slugify
from .utils import read_time
class Category(models.Model):
name = models.CharField(max_length=120, unique=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
slug = models.SlugField(unique=True)
def __str__(self):
return self.name
def save(self, *args, **kwargs):
if not self.id: # to prevent changing slug on updates
self.slug = slugify(self.name)
return super(Category, self).save(*args, **kwargs)
def upload_location(instance, filename):
return '%s/%s'%(instance.id, filename)
class PostManager(models.Manager):
def active(self):
return super(PostManager, self).filter(draft=False, published__lte=timezone.now())
class Post(models.Model):
title = models.CharField(max_length=120)
slug = models.SlugField(unique=True)
content = models.TextField()
author = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
image = models.ImageField(
upload_to=upload_location,
null=True,
blank=True)
timestamp = models.DateTimeField(auto_now_add=True, auto_now=False)
updated = models.DateTimeField(auto_now_add=False, auto_now=True)
published = models.DateField(auto_now=False, auto_now_add=False)
draft = models.BooleanField(default=False)
category = models.ManyToManyField(Category)
read_time = models.IntegerField(default=0)
objects = PostManager()
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('posts:detail', kwargs={'pk': self.pk})
def save_no_img(self):
self.image = None
return super(Post, self).save()
def create_slug(instance, new_slug=None):
slug = slugify(instance.title)
if new_slug is not None:
slug = new_slug
qs = Post.objects.filter(slug=slug).order_by("-id")
exists = qs.exists()
if exists:
new_slug = "%s-%s" %(slug, qs.first().id)
return create_slug(instance, new_slug=new_slug)
return slug
def pre_save_post_receiver(sender, instance, *args, **kwargs):
if not instance.slug:
instance.slug = create_slug(instance)
html_content = instance.content
instance.read_time = read_time(html_content)
pre_save.connect(pre_save_post_receiver, sender=Post)
my forms.py:
from django import forms
from .models import Post
from pagedown.widgets import PagedownWidget
class PostForm(forms.ModelForm):
published = forms.DateField(widget=forms.SelectDateWidget)
content = forms.CharField(widget=PagedownWidget())
class Meta:
model = Post
# fields = ['author', 'title', 'content', 'image', 'draft', 'published', 'category']
exclude = ['objects', 'updated', 'timestamp', 'slug']
test_forms.py:
import pytest
from .. import forms
from posts.models import Category
from mixer.backend.django import mixer
pytestmark = pytest.mark.django_db
class TestPostForm():
def test_empty_form(self):
form = forms.PostForm(data={})
assert form.is_valid() is False, 'Should be invalid if no data is given'
def test_not_empty_form(self):
staff_user = mixer.blend('auth.User', is_staff=True)
category = mixer.blend('posts.Category')
data={'content': 'some content',
'author': staff_user,
'title': 'some title',
'category': category,}
form = forms.PostForm(data=data)
assert form.is_valid() is True, 'Should be valid if data is given'
update:
collected more specific errors using:
assert form.errors == {}, 'should be empty'
errors:
{'author': ['Select a valid choice. That choice is not one of the
available choices.'],
'category': ['Enter a list of values.'],
'published': ['This field is required.'],
'read_time': ['This field is required.']}
how to address them?
update 2:
as Nadège suggested I modified data to include published and read_time, changed category into a list and created a user without mixer.
staff_user = User.objects.create_superuser(is_staff=True,
email='oo#gm.com',
username='staffuser',
password='somepass')
category = mixer.blend('posts.Category')
today = date.today()
data={'content': 'some content',
'author': staff_user,
'title': 'some title',
'published': today,
'read_time': 1,
'category': [category],}
There is still error regarding the 'author':
{'author': ['Select a valid choice. That choice is not one of the
available choices.']}
update 3:
for some reason 'author' had to be provided as an id, the working code for this test looks like this:
class TestPostForm():
def test_empty_form(self):
form = forms.PostForm(data={})
assert form.is_valid() is False, 'Should be invalid if no data is given'
def test_not_empty_form(self):
staff_user = mixer.blend('auth.User')
category = mixer.blend('posts.Category')
today = date.today()
data={'content': 'some content',
'author': staff_user.id,
'title': 'some title',
'published': today,
'read_time': 1,
'category': [category],}
form = forms.PostForm(data=data)
assert form.errors == {}, 'shoud be empty'
assert form.is_valid() is True, 'Should be valid if data is given'
Ok so when you have an invalid form, first thing is to check why, so the errors of the form. With this new information we can fix each problem.
Your form has 4 validations errors. The last two are pretty straightforward.
'published': ['This field is required.'],
'read_time': ['This field is required.']
Those two fields in your form are required but you didn't filled them.
So you have two options,
Add a value for those fields in the data you give to the form
Remove the fields from the form: add them to exclude
You can also set the published field a not required like this:
published = forms.DateField(widget=forms.SelectDateWidget, required=False)
for read_time, the field is required or not, depending on the corresponding field in the model. If the model field is not nullable, the field in the form is set as required.
Next there is
'category': ['Enter a list of values.']
You provided a value but the type is not what was expected.
category in your model is ManyToMany so you can't give just one category, it must be a list (even if it has only one element!)
'category': [category],
Finally the author,
'author': ['Select a valid choice. That choice is not one of the available choices.']
There too you provided a value that is not valid. The validation doesn't recognize the value as a proper auth.User. I'm not familiar with Mixer, so maybe ask a new question specifically about Mixer and Django Forms with a ForeignKey.

How to raise forms validation error when ever the user try's to create an object with the object twice

I'v used the unique together model meta, it works but it just comes up with this error. i want to raise a forms validation error, rather than a IntegrityError.
IntegrityError at /name/
UNIQUE constraint failed: canvas_canvas.user_id, canvas_canvas.canvas_name
Request Method: POST
Request URL: http://127.0.0.1:8000/name/
Exception Type: IntegrityError
Exception Value:
UNIQUE constraint failed: canvas_canvas.user_id, canvas_canvas.canvas_name
Exception Location: C:\Users\AppData\Local\Programs\Python\Python35-32\lib\site-packages\django\db\backends\sqlite3\base.py in execute, line 337
class Canvas(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, default=1)
canvas_name = models.CharField(
max_length=100,
validators=[
# validate_canvas_title,
RegexValidator(
regex=CANVAS_REGEX,
message='Canvas must only contain Alpahnumeric characters',
code='invalid_canvas_title'
)],
)
slug = models.SlugField(max_length=100, blank=True)
background_image = models.ImageField(
upload_to=upload_location,
null=True,
blank=True,
)
# sort catergory into alphabetical order
category = models.ForeignKey('category.Category', default=1, blank=True)
followers = models.ManyToManyField(settings.AUTH_USER_MODEL, related_name='canvas_followed_by', blank=True)
class Meta:
unique_together = ['user', 'canvas_name']
form
class CanvasModelForm(ModelForm):
class Meta:
model = Canvas
fields = ['canvas_name', 'category', 'background_image']
widgets = {
'canvas_name': TextInput(attrs={'class': 'form-input'}),
'category': Select(attrs={'class': 'form-input'}),
}
view
user = get_object_or_404(User, username=username)
form_create = CanvasModelCreateForm(request.POST or None)
if form_create.is_valid():
instance = form_create.save(commit=False)
instance.user = request.user
instance.save()
return redirect('canvases:canvas', username=request.user.username, slug=instance.slug)
template = 'pages/profile.html'
context = {
'user': user,
'form_create': form_create,
}
return render(request, template, context)
You could do this by passing request.user into the form and use it for validating the canvas_name.
You need to override the form's __init__ method to take an extra keyword argument, user. This stores the user in the form, where it's required, and from where you can access it in your clean method.
def __init__(self, *args, **kwargs):
self.user = kwargs.pop('user', None)
super(CanvasModelCreateForm, self).__init__(*args, **kwargs)
def clean_canvas_name(self):
canvas_name = self.cleaned_data.get('canvas_name')
if Canvas.objects.get(user=self.user, canvas_name=canvas_name).exists():
raise forms.ValidationError(u'Canvas with same name already exists.')
return canvas_name
And you should change in your view like this so,
form_create = CanvasModelCreateForm(request.POST, user=request.user)

Django SlugField not unique

I'm stuck from a couple hours and I can't solve this problem.
The following code works well, but if I write a "Title" that already exist I get:
UNIQUE constraint failed: appname_shopaccount.url_shop
Model
class ShopAccount(models.Model):
user = models.ForeignKey(User)
title = models.CharField(max_length=150)
url_shop = models.SlugField(max_length=200, unique=True)
company_name = models.CharField(max_length=200)
def save(self, *args, **kwargs):
self.url_shop = slugify(self.title)
super(ShopAccount, self).save(*args, **kwargs)
def __str__(self):
return self.title
Forms
class SignUpShopForm(ModelForm):
class Meta:
model = ShopAccount
fields=['title', 'company_name']
exclude= ('user',)
error_messages = {
'title': {
'required': "Enter a Shop name",
},
'company_name': {
'required': "Enter a Company name",
}
}
View
def signup_shop(request):
if request.POST:
form = SignUpShopForm(request.POST)
if form.is_valid():
account = form.save(commit=False)
account.user = request.user
account.save()
return HttpResponseRedirect('/account/updated/')
else:
form = SignUpShopForm(data=request.POST)
return render_to_response('register_shop.html', { 'form':form }, context_instance=RequestContext(request))
else:
return render_to_response('register_shop.html', context_instance=RequestContext(request))
How can I solve this problem?
Remove unique=True from the url_shop field in your model and update your database with python manage.py makemigrations and python manage.py migrate.
Be aware though that a slug field that can have non-unique values might have consequences for your app depending on how you are using it (two objects having the same url for example)
You can use unique_slugify to force a unique slug- https://djangosnippets.org/snippets/690/
import unique_slugify
class ShopAccount(models.Model):
...
def save(self, *args, **kwargs):
self.url_shop = unique_slugify(self, self.title, slug_field_name='url_shop')
super(ShopAccount, self).save(*args, **kwargs)
If the slug being created already exists, unique_slugify will append a '-1' (and on upwards) to the slug until a unique slug is found. Keep unique=True in your url_shop model kwargs :]
The reason you have an issue with your existing code is that slugify only converts the title field to a slug, not checking for existing slug values. With the method above, unique_slugify will check existing values and generate a slug that does not exist in the db yet.