How to test foreignkeys to self in django - django

Django allows a foreign key to "self" as in
class Profile(models.Model):
user = models.OneToOneField(
User,
on_delete=models.CASCADE,
verbose_name="User",
related_name="user_profiles",
)
entity = models.ForeignKey(
Entity,
on_delete=models.CASCADE,
verbose_name="Entity",
related_name="entity_profiles",
)
email = models.EmailField(
max_length=255,
help_text=_("Automatically generated to use entity email domain"),
)
supervisor0 = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name="Supervisor0",
related_name="supervisor0_profiles",
help_text=_("Employees only."),
)
supervisor1 = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name="Supervisor1",
related_name="supervisor1_profiles",
help_text=_(
"Employees only. Only the executive head is his/her own \
supervisor."
),
)
supervisor2 = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name="Supervisor2",
related_name="supervisor2_profiles",
help_text=_(
"Employees only. Only the executive head is his/her own \
supervisor."
),
)
supervisor3 = models.ForeignKey(
"self",
on_delete=models.CASCADE,
null=True,
blank=True,
verbose_name="Supervisor3",
related_name="supervisor3_profiles",
help_text=_(
"Employees only. Only the executive head is his/her own \
supervisor."
),
)
The supervisor0 is the user and being supervisor0 identifies him/her as an employee. The other supervisors also have to be already in the database for them to be able to be referenced.
The help_texts explain the situation of the executive head.
The question is how to test these relationships to "self". I have no problem with relationships to other models.
Using pytest, I record only supervisor0 in the ProfileFactory for the moment.
class ProfileFactory(factory.django.DjangoModelFactory):
class Meta:
model = Profile
user = factory.SubFactory(UserFactory)
entity = factory.SubFactory(EntityFactory)
supervisor0 = user
An assertion error should be thrown up if there is no supervisor1:
def test_profile_supervisor0_and_no_supervisor1():
left = "supervisor0" and "supervisor1"
right = "supervisor0" and not "supervisor1"
assert left == right
It is! The problem is the intergrity errors which accompy the results.
They say
ERROR internalcontrol/tests/test_models.py::test_profile_str - django.db.utils.IntegrityError: duplicate key value violates unique constraint "users_user_username_key"
ERROR internalcontrol/tests/test_models.py::test_profile_get_absolute_url - django.db.utils.IntegrityError: duplicate key value violates unique constraint "users_user_username...
They refer to previous string and get_absolute_url tests which pass in the absence of the "self" relationship.
The problem is - how does one test foreign keys to self?

Related

Django / OnetoMany relation within the same class

Here is my models.py
class Scenes(models.Model):
name = models.SlugField('Scene name', max_length=60,unique=True)
record_date = models.DateTimeField('Scene date')
manager = models.ForeignKey(
settings.AUTH_USER_MODEL,
blank=True,
null=True,
on_delete=models.SET_NULL)
description = models.TextField(blank=True)
previous = models.OneToOneField(
'self',
blank=True,
null=True,
related_name='next',
on_delete=models.SET_NULL
)
I have started with one instance : Scene1
The problem is I want to have on to many choices and not one to one like explained below
Nevertheless, When I change to
previous = models.ManyToManyField(
Then I also get an error about on_delete :
TypeError: init() got an unexpected keyword argument 'on_delete'
What would be the best djangonic way ?
As I understand, your requirements are:
One scene can have only one previous scene
One scene can have many next scenes
You are almost there. Instead of OneToOneField, use ForeignKey with self reference:
previous = models.ForeignKey('self', related_name='next', ...)

django: smart-select ChainedForeignKey / chained dropdown in admin

Hej! :)
I have 5 models which are connected hierarchical with each other.
Section -> division -> group -> class -> wz
one section can have multiple divisions, but one division can only have one section (and so on). Therefor I have ForeignKeys set:
# models.py
class NaceSection(models.Model):
code = models.CharField(max_length=1, unique=True)
description_english = models.CharField(max_length=500)
class NaceDivision(models.Model):
code = models.CharField(max_length=2, unique=True)
nace_section = models.ForeignKey(NaceSection, on_delete=models.CASCADE, related_name="nace_section")
description_english = models.CharField(max_length=500)
class NaceGroup(models.Model):
nace_division = models.ForeignKey(NaceDivision, on_delete=models.CASCADE, related_name="nace_division")
code = models.CharField(max_length=4, unique=True)
description_english = models.CharField(max_length=500)
I than have a model where all those are integrated as M2M fields with a dropdown option.
My goal is to only get the divisions which are in the already selected section in the admin area. (and so on)
I tried smart-select ChainedForeignKey:
# models.py
class Institution(models.Model):
nace_sections = models.ManyToManyField(
NaceSection,
related_name="nace_sections"
)
nace_divisions = ChainedForeignKey(
NaceDivision,
chained_field="nace_sections",
chained_model_field='nace_sections',
blank=True,
)
nace_group = ChainedForeignKey(
NaceGroup,
chained_field="nace_divisions",
chained_model_field='nace_divisions',
blank=True,
)
The organisation and dropdown in the admin area do not change at all and my view with a table of all my results tells me ('42S22', "[42S22] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Invalid column name 'nace_divisions_id'. (207) (SQLExecDirectW)")
With the ChainedManyToManyField nothing at all happens. Does anybody know what's going wrong?
Any help appreciated! :)
nace_sections is specified as models.ManyToManyField in your code
1.Change from ManyToManyField to ForeignKey.
2.Rename chained_model_field values
-->nace_sections to nace_section
-->nace_divisions to nace_division
class Institution(models.Model):
nace_sections = models.ForeignKey(
NaceSection,
related_name="nace_sections"
, on_delete=models.CASCADE
)
nace_divisions = ChainedForeignKey(
NaceDivision,
chained_field="nace_sections",
chained_model_field='nace_section',
blank=True,
)
nace_group = ChainedForeignKey(
NaceGroup,
chained_field="nace_divisions",
chained_model_field='nace_division',
blank=True,
)

Django admin form raises IntegrityError for model with conditional UniqueConstraint

I was asked to add some logic to model uniqueness.
Each Payment must have either transaction_id or payment_id filled.
Each Payment is identificated by (transaction_id, operation, payment_created_date) or (payment_id, operation, payment_created_date).
On database level this works fine.
Inserting Payment with same transaction_id, operation, payment_created_date twice causes unique constraint violation.
For this model i created admin page. But inserting same row with admin page causes IntegrityError at /admin/finance/payment/add/ duplicate key value violates unique constraint "unique_finance_payment_without_payment_id" DETAIL: Key (transaction_id, operation, payment_created_date)=(dasdasd, Refund, 2021-10-04) already exists. instead of simple user-friendly admin error Please correct the error below. Payment with this Transaction id, Operation and Payment created date already exists. How to make Django admin catch this IntegrityError and show it in admin form?
here is my models.py
class ReportName(DiModel):
name = CharField(
max_length=50,
blank=False,
null=False)
def __str__(self):
return self.name
class Payment(DiModel):
class Meta:
unique_together = ('transaction_id', 'payment_id', 'operation', 'payment_created_date')
constraints=[UniqueConstraint(fields=['transaction_id', 'operation', 'payment_created_date'],
condition=Q(payment_id=None),
name='unique_finance_payment_without_payment_id'),
UniqueConstraint(fields=['payment_id', 'operation', 'payment_created_date'],
condition=Q(transaction_id=None),
name='unique_finance_payment_without_transaction_id'),]
OPERATION_TYPE = [
('Refund', 'Refund'),
('Charge', 'Charge'),
]
CURRENCY_CODE = [
('EUR', 'EUR'),
('RUB', 'RUB'),
('USD', 'USD'),
('GBP', 'GBP'),
('AUD', 'AUD'),
('PLN', 'PLN'),
('SGD', 'SGD'),
('MYR', 'MYR'),
('RON', 'RON'),
('ZAR', 'ZAR'),
]
report_name = ForeignKey(ReportName,
on_delete=models.PROTECT,
blank=False,
null=False,
help_text="Processor and report type")
operation = CharField(max_length=6,
choices=OPERATION_TYPE,
blank=False,
null=False,
default=OPERATION_TYPE[0][0],
help_text='Payment operation type')
payment_created_date = DateField(blank=False,
null=False,
default=timezone.now)
amount = DecimalField(blank=True,
null=True,
max_digits=10,
decimal_places=2,
help_text='Payment amount')
commission = DecimalField(blank=False,
null=False,
max_digits=10,
decimal_places=2,
help_text='Transaction fee')
currency = CharField(max_length=3,
choices=CURRENCY_CODE,
blank=False,
null=False,
default=CURRENCY_CODE[0][0],
help_text='Amount and commission currency code')
transaction_id = CharField(max_length=32,
blank=True,
null=True,
help_text='Payota transaction id')
payment_id = IntegerField(blank=True,
null=True,
help_text='Payota payment id')
author_user = models.ForeignKey(User,
on_delete=models.PROTECT,
null=True,
related_name='finance_author_user')
def __str__(self):
return f"{self.report_name} {self.operation} {self.created_at}"
and this is my admin.py
class PaymentForm(forms.ModelForm):
class Meta:
fields = (
'report_name',
'operation',
'payment_created_date',
'amount',
'commission',
'currency',
'transaction_id',
'payment_id',
)
#admin.register(Payment)
class PaymentAdmin(ImportExportMixin, admin.ModelAdmin):
form = PaymentForm
list_display = [
'report_name',
'operation',
'payment_created_date',
'amount',
'commission',
'currency',
'transaction_id',
'payment_id',
]
list_filter = (
'report_name',
'operation',
('payment_created_date', DateRangeFilter),
)
search_fields = ['report_name__name', 'operation']
resource_class = PaymentResource
class Meta:
model = Payment

Django Model Relation entries

I have data and I want to organize it as:
I create three tables, on for MonthsM, Wellinfo and TestPRODM:
class MonthsM(models.Model):
MonthsM = models.DateTimeField(unique=True)
class Wellinfo (models.Model):
WellID = models.CharField(max_length=15,unique=True)
class TestPRODM(models.Model):
WellID = models.ForeignKey(Wellinfo ,to_field='WellID', on_delete=models.CASCADE)
TestMonth = models.ForeignKey(TestMonthM, on_delete=models.CASCADE)
TestOIL = models.DecimalField(max_digits=10, decimal_places=3,null=True, blank=True)
#conditions
In my real data I have one WellID that can be tested only once in month.
so how to set a condition that do that?
so in the table (TestPRODM) it can have many entry with same WellID but with different months
and so it can have many entry with same TestMonth but with different WellID.
NB. NO entry in (TestPRODM) table that have the same WellID and TestMonth in the same time.
Updated POST
for i ,value in enumerate(PUITS):
try:
_, create = TestPRODM.objects.update_or_create(
WellID= PUITS[i],
TestMonth_id =obj.id,
TestOIL =float(QHUILE[i]),
)
print('Saving', PUITS[i])
AddedList +=[value]
except:
print('Passed', PUITS[i])
pass
and this function pass all restrictions in model and save all data in database!
You make the combination of WellID and TestMonth unique, with a UniqueConstraint [wiki]:
class TestPRODM(models.Model):
WellID = models.ForeignKey(
Wellinfo,
to_field='WellID',
on_delete=models.CASCADE
)
TestMonth = models.ForeignKey(
TestMonthM,
on_delete=models.CASCADE
)
TestOIL = models.DecimalField(
max_digits=10,
decimal_places=3,
null=True,
blank=True
)
class Meta:
constraints = [
models.UniqueConstraint(
fields=['WellID', 'TestMonth'],
name='once_per_month'
)
]
prior to django-2.2, you can make use of unique_together [Django-doc]:
class TestPRODM(models.Model):
WellID = models.ForeignKey(
Wellinfo,
to_field='WellID',
on_delete=models.CASCADE
)
TestMonth = models.ForeignKey(
TestMonthM,
on_delete=models.CASCADE
)
TestOIL = models.DecimalField(
max_digits=10,
decimal_places=3,
null=True,
blank=True
)
class Meta:
unique_together=[['WellID', 'TestMonth']]
Note: normally the name of the fields in a Django model are written in snake_case, not PerlCase, so it should be: test_month instead of TestMonth.

ProgrammingError: column specified more than once

I'm attempting to run 'syncdb -all' in my django project, having just added this model.
However I'm getting this error message:
django.db.utils.ProgrammingError: column "target_content_type_id" specified more than once
Why is this happening when 'target_content_type_id' is not repeated and not in any other models?
class Action(models.Model):
actor = models.ForeignKey(User)
verb = models.CharField(max_length=500)
action_object_content_type = models.ForeignKey(
ContentType, related_name='action_object', blank=True, null=True
)
action_object_object_id = models.CharField(
max_length=500, blank=True, null=True
)
action_object = generic.GenericForeignKey(
'action_object_content_type', 'action_object_object_id'
)
target_content_type = models.ForeignKey(
ContentType, related_name='target', blank=True, null=True
)
target_content_type_id = models.CharField(
max_length=500, blank=True, null=True
)
target = generic.GenericForeignKey(
'target_content_type', 'target_content_type_id'
)
public = models.BooleanField(default=True)
timestamp = models.DateTimeField(auto_now_add=True)
When you specify a ForeignKey field, Behind the scenes, Django appends "_id" to the field name to create its database column name.
In this case, your field target_content_type which is a ForeignKey, would correspond to the database column target_content_type_id, which is conflicting with your charfield.
Rename your target_content_type_id to something else like target_content_type_object_id or something unique.
Here is the documentation of ForeignKey and more specifically on Database Representation