Django Many To Many Complaining about PrimaryKey - python

I have three models that are related. The first is called DayOfWeek, which is juts a day label and number. It looks like this:
class DayOfWeek(models.Model):
day = models.IntegerField()
label = models.CharField(max_length='20')
def __str__(self):
return self.label
This class is populated using a fixture every time I syncdb.Next, I have an event model, it looks like this:
class Event(AnnouncementBase, Location):
cost = CurrencyField(decimal_places=2, max_digits=10, blank=True, default=0.00)
start_date = models.DateField(default = datetime.now().date())
start_time = models.TimeField(default = datetime.now().time())
end_date = models.DateField(blank=True, default=None, null = True)
end_time = models.TimeField(blank=True, default=None, null = True)
Finally, there is a recurrence. It has an event and is used to schedule the event for recurring events. It looks like this:
class Recurrence(models.Model):
event = models.ForeignKey(Event, related_name='event')
repeats = models.CharField(max_length = 50, choices = EVENT_REPEAT_CHOICES)
repeat_every = models.IntegerField(default = 1)
repeat_on = models.ManyToManyField(DayOfWeek, blank=True, null=True)
repeat_by = models.CharField(max_length = 50, choices = EVENT_REPEAT_BY_CHOICES, blank=True)
repeat_by_day_of_month = models.IntegerField(default = 0, blank=True)
repeat_ends = models.CharField(max_length = 50, choices = EVENT_REPEAT_END_CHOICES)
end_occurrences = models.IntegerField(default = 0, blank=True)
repeat_end_date = models.DateField(blank=True, default=None, null = True)
past_event_count = models.IntegerField(default=0, blank=True)
scheduled_events = models.ManyToManyField(Event, blank=True, default=None, related_name = 'scheduled_events')
is_active = models.BooleanField(blank=True, default=True)
def save(self, force_insert=False, force_update=False, using=None):
"""Overridden to create events the first time."""
self.full_clean()
#First do normal save so the data is there for the even scheduler.
self.save_base(force_insert=force_insert, force_update=force_update, using=using)
#If nothing is scheduled yet, schedule the first batch
if self.scheduled_events.count() == 0 and self.past_event_count == 0:
self.scheduleEvents()
def clean(self):
#repeat on weekly
if self.repeat_every < 1:
raise ValidationError('Repeat every must be at least 1.')
#weekly
if self.repeats == EVENT_REPEAT_CHOICES[1][0]:
#look for missing stuff
if not self.repeat_on:
raise ValidationError('Missing repeat on.')
Finally, I have a unit test that checks this works ok it looks like this:
def test_weekly_mon_wed_fri_occurrence(self):
event = Event()
event.start_date = date(year=2012, month=1, day=2)
event.start_time = time(hour=13, minute=30)
event.save()
recurrence = Recurrence()
recurrence.repeats = EVENT_REPEAT_CHOICES[1][0]
recurrence.repeat_on = (EVENT_DAY_CHOICES[1][0], EVENT_DAY_CHOICES[3][0], EVENT_DAY_CHOICES[5][0])
recurrence.repeat_ends = EVENT_REPEAT_END_CHOICES[0][0]
recurrence.event = event
nextEvent = recurrence.getNextEvent(event)
self.assertEquals(date(year=2012, month=1, day=4), nextEvent.start_date)
self.assertEquals(event.start_time, nextEvent.start_time)
nextNextEvent = recurrence.getNextEvent(nextEvent)
self.assertEquals(date(year=2012, month=1, day=6), nextNextEvent.start_date)
self.assertEquals(event.start_time, nextNextEvent.start_time)
Whenever the test is run it fails, with the following exception.
ValueError: 'Recurrence' instance needs to have a primary key value before a many-to-many relationship can be used.
The error happens on the line if self.repeat_on in the clean method.
I want repeat_on to be optional, only some types of recurrences need it. How do I make this work? What am I missing that is causing it to fail?

You need to call recurrence.save() before assigning Many2Many relationships. In your code you do
recurrence.repeat_on = (EVENT_DAY_CHOICES[1][0], EVENT_DAY_CHOICES[3][0], EVENT_DAY_CHOICES[5][0])
without saving the recurrence first. Because its not saved, recurrence does not have primary key generated yet, and Django ORM doesn't know what to insert as a foreign key into the M2M table.

Related

Refactor Business Logic From View To Model

I've got a django view with a small amount of business logic in it. However it would be useful to use this information elsewhere so it feels like something that I could put into a model however I'm not entirely sure how to go about doing this. Mainly because I need specific user data. This is what I have so far:
views.py
def create_budget(self, context):
starting_point = BudgetStartingPoint.objects.filter(owner=self.request.user)
running_tally = starting_point[0].starting_amount
budget_dict = {}
for transaction in self.get_queryset():
if transaction.income_type == "IN":
running_tally += transaction.transaction_amount
else:
running_tally -= transaction.transaction_amount
budget_dict[transaction] = [running_tally, transaction.income_type]
context['budget'] = budget_dict
models.py
class BudgetTransaction(models.Model):
"""
Individual transaction for Budget
"""
transaction_types = [
('fixed', 'Fixed'),
('extra', 'Extra'),
]
income_types = [
("IN", "Income"),
("OUT", "Expense"),
]
frequencies = [
('weeks', 'Weekly'),
('fort', 'Fortnightly'),
('4week', 'Four Weeks'),
('months', 'Monthly'),
('years', 'Yearly'),
]
today = datetime.today().date()
id = HashidAutoField(
primary_key=True, salt=f"transaction{settings.HASHID_FIELD_SALT}"
)
owner = models.ForeignKey(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
help_text="Owner of the item"
)
category = models.ForeignKey(BudgetCategory, on_delete=models.CASCADE, default=1, null=True)
transaction_type = models.CharField(max_length=40, choices=transaction_types, default=1)
transaction_name = models.CharField(max_length=100, null=False)
transaction_amount = models.FloatField(null=False, default=100)
income_type = models.CharField(max_length=20, choices=income_types, default="IN")
next_date = models.DateField(null=False, default=today)
frequency = models.CharField(max_length=20, choices=frequencies, default=1)
complete = models.BooleanField(default=False)
def __str__(self):
return self.transaction_name
class Meta:
ordering = ['next_date']
I feel like I have a lot of things that would be nice to have as a model method but they need to get the current user which I'm not sure how to do.
Not sure if this is what you mean but you can do:
def create_budget(self, context):
starting_point = BudgetStartingPoint.objects.filter(owner=self.request.user)
running_tally = starting_point[0].starting_amount
budget_dict = {}
for transaction in self.get_queryset():
running_tally = transaction.get_change(running_tally)
budget_dict[transaction] = [running_tally, transaction.income_type]
context['budget'] = budget_dict
class BudgetTransaction(models.Model):
....
def get_change(self,value):
return value + (
self.transaction_amount
if self.income_type == "IN" else
-self.transaction_amount
)

Not null constraint failed. Django Models-Postgres Same error null=True as null=False

I'm having difficulty updating these three models which are linked with Foreign keys
Reason they are linked with foreign keys is Events can have multiple Markets and Markets can have multiple Runners.
I'm at my wits end with this not null error. Even If I have a field that causes the issue and I remove the field from my model. Make migrations, migrate and remove the save from my task I still get the exact same error. What is the purpose of this not null argument? Even If I have null=True or null=False on brand new models I still get the error. Not null constraint failed
django.db.utils.IntegrityError: NOT NULL constraint failed: testapp_ma.event_id
I've no idea why this is failing.
Do I need to make sure all fields have a null argument? According to django documentation default is false. For each object my task runs all fields have data. So default of false should be fine.
Could this be due to the manner I'm updating the models with my task?
full stacktrace here https://gist.github.com/Cally99/08fed46ba8039fa65d00f53e8a31b37a
class Event(models.Model):
sport_name = models.CharField(max_length=15)
event_id = models.BigIntegerField(unique=True, null=False)
event_name = models.CharField(max_length=200)
start_time = models.DateTimeField()
status = models.CharField(max_length=13)
class Market(models.Model):
event = models.ForeignKey(Event, on_delete=models.CASCADE)
market_id = models.BigIntegerField(unique=True)
market_name = models.CharField(max_length=35)
status = models.CharField(max_length=10)
volume = models.FloatField(null=True)
class Runner(models.Model):
market = models.ForeignKey(Market, null=True, default=None, on_delete=models.SET_NULL)
runner_id = models.BigIntegerField(unique=True)
event_id = models.BigIntegerField(null=True, default=0)
name = models.CharField(max_length=100)
tasks.py
#shared_task(bind=True)
def get_events(self):
api = get_client()
events = api.market_data.get_events(sport_ids=[9],states=MarketStates.All,
per_page=200, offset=0,
include_event_participants=Boolean.T,
category_ids=None, price_depth=3,
side=Side.All, session=None)
for event in events:
event_name = event["name"]
event_id = event['id']
start_time = event['start']
status = event["status"]
ev, created = Ev.objects.update_or_create(event_id=event_id)
ev.event_name = event_name
ev.start_time = start_time
ev.status = status
ev.save()
markets = event["markets"]
for market in markets:
event_id = market['event-id']
market_id = market['id']
market_name = market['name']
status = market['status']
volume = market['volume']
ma, created = Ma.objects.update_or_create(market_id=market_id)
ma.market_name = market_name
ma.status = status
ma.volume = volume
ma.save()
runners = market["runners"]
for runner in runners:
name = runner['name']
runner_id = runner['id']
event_id = runner['event-id']
runner, created = Ru.objects.update_or_create(runner_id=runner_id)
runner.event_id = event_id
runner.name = name
runner.save()
With the line
Ma.objects.update_or_create(market_id=market_id)
you haven't set the Event for your market object. And given how
event = models.ForeignKey(Event, on_delete=models.CASCADE)
doesn't have null=True, it won't accept any null values.

Django : Updating model by overriding save method

This is my first model
from django.db import models
from django.contrib.auth.models import User
CHOICES = (('Earned Leave','Earned Leave'),('Casual Leave','Casual Leave'),('Sick Leave','Sick Leave'),('Paid Leave','Paid Leave'))
STATUS_CHOICES = (('0', 'Rejected'),('1', 'Accepted'),)
class Leave(models.Model):
employee_ID = models.CharField(max_length = 20)
name = models.CharField(max_length = 50)
user = models.ForeignKey(User, on_delete = models.CASCADE, null =True)
type_of_leave = models.CharField(max_length = 15, choices = CHOICES)
from_date = models.DateField()
to_date = models.DateField()
status = models.CharField(max_length = 15, choices = STATUS_CHOICES)
#property
def date_diff(self):
return (self.to_date - self.from_date).days
This is my second model
class History(models.Model):
first_name = models.CharField(max_length = 50)
last_name = models.CharField(max_length = 50)
employee_ID = models.CharField(max_length = 20)
earned_leave = models.IntegerField()
casual_leave = models.IntegerField()
sick_leave = models.IntegerField()
paid_leave =models.IntegerField()
Here upon saving the first model Leave, I have written to override save method like,
def save(self, *args, **kwargs):
super(Leave, self).save()
if self.employee_ID == History.employee_ID:
if self.status == '1':
if self.type_of_leave == 'Earned Leave':
history = History.objects.update(
earned_leave = self.date_diff,
)
But upon saving the first model, all the entries in the History model are getting updated. Where in the history table every user have a separate entry with user's details(first_name, last_name, employee_ID) and default values as 10 for the rest. Upon saving the Leave model only the entry that is associated with the employee_ID of Leave model should be updated in the History model. For that purpose i have given as if self.employee_ID == History.employee_ID: but it isn't working.
I've even tried as -
def save(self, *args, **kwargs):
super(Leave, self).save()
if self.employee_ID == History.employee_ID:
if self.status == '1':
if self.type_of_leave == 'Earned Leave':
history = History.objects.update(
earned_leave = History.earned_leave - self.date_diff,
)
But this is not working, nothing gets updated and get'a an error unsupported operand types
So, the base of the project is employee-leave-management. As the user applies for the leave and is accepted the number of days left should get updated in History table or model.
If there's any alternate method, share.
History.objects.update(...) does in fact update all the History objects. You should specify which ones you want to update in your query:
from django.db.models import F
def save(self, *args, **kwargs):
super(Leave, self).save()
if self.status == '1':
if self.type_of_leave == 'Earned Leave':
history = History.objects.filter(employee_id=self.employee_id).update(
earned_leave = F('earned_leave') - self.date_diff,
)
The F() expression here refers to the value of the column and will be computed by the database rather than in Python.

Django: Is it OK to set a field to allow null which is assigned in clean func?

My Career model has fields, since, until, and currently_employed.
# resume/models.py
...
class Career(models.Model):
resume = models.ForeignKey(Resume, on_delete=models.CASCADE)
since = models.DateField()
until = models.DateField(blank=True, null=True)
currently_employed = models.BooleanField()
company = models.CharField(max_length=50)
position = models.CharField(max_length=50)
achivement = models.TextField(default='')
I'd like to set until to current date if currently_employed is checked in the django(python) code, not in the template(html/js), as possible.
# resume/forms.py
...
class CareerForm(forms.ModelForm):
...
def clean(self):
cleaned_data = super().clean()
if cleaned_data['currently_employed'] == True:
cleaned_data['until'] = timezone.now().date()
# Check since < until
if cleaned_data['since'] >= cleaned_data['until']:
raise ValidationError('"Until" should be later than "Since"')
...
Is it OK to set the until nullable?(and its form field set required=False)
However, it would not be null in my logic since by currently employed, or user put that.
Keep the 'until' field as null in the database. If you set it to the current date then it's incorrect the following day as long as the user is still employed. You can still have a convenience which returns the current date if the user is still employed. This also means that the currently_employed database field becomes redundant.
class Career(models.Model):
...
_until = models.DateField(blank=True, null=True)
#property
def until(self):
return self._until or timezone.now().date()
#property
def currently_employed(self):
return self._until is None

Did I set this up correctly?

I'm asking if I set up the create method up correctly. Or does it need to be added for the other two models as well? How would this be changed?
class PointModel(models.Model):
x = models.IntegerField()
y = models.IntegerField()
index = models.IntegerField()
class DatetimeRangeModel(models.Model):
start_datetime = models.CharField(max_length=14)
end_datetime = models.CharField(max_length=14)
class PlanModel(models.Model):
data_number = models.IntegerField()
data_datetime_range = models.ForeignKey(DatetimeRangeModel, blank=True, null=True, on_delete=models.SET_NULL)
data_polygon = models.ForeignKey(PointModel, blank=True, null=True, on_delete=models.SET_NULL)
#classmethod
def create(cls, data_number, data_datetime_range, data_polygon):
plan = cls(data_number=data_number, data_datetime_range = data_datetime_range,
data_polygon=data_polygon)
return plan
EDIT: I change the structure which fixed the undefined and added some logic that prevents the PlanModel from being deleted with the "blank=True, null=True, on_delete=models.SET_NULL"
Does this look right?
see the docs for creating objects
#classmethod
def create(cls, title):
book = cls(title=title)
# do something with the book
return book
there's no much reason to add those unless you have something to add there on the # do something with the book line
EDIT: instead of calling create you're usually do:
plan = PlanModel(data_number=1, ....)
plan.save()
or sometimes:
plan = PlanModel()
plan.data_number=1
...
plan.save()

Categories