Creating an ID that rolls over by year - django

I have an invoice app that has an ID. I'd like to have it rollover by year. That is, every year, it would start back at 001. My return string would be 2014-001, 2014-002, 2015-001...
However, Django doesn't seem to support composite keys. Is there a way to do this?
class Invoice(models.Model):
description = models.CharField(max_length=200)
date = models.DateField('Date Issued')
client = models.ForeignKey('organizations.Contact')
UNPAID = 'Unpaid'
PAID = 'Paid'
STATUS_CHOICES = (
(UNPAID, 'Unpaid'),
(PAID, 'Paid'),
)
status = models.CharField(max_length=6,
choices=STATUS_CHOICES, default=UNPAID)
notes = models.TextField(blank=True)
def __str__(self):
return (self.date.__str__().replace("-", "") +
"-" + self.id.__str__().zfill(3))
edit new code:
def save(self, *args, **kwargs):
if self.invoice_id is "":
current_date = self.date.year
previous_invoice = Invoice.objects.filter(invoice_id__contains=current_date).order_by('-id').first()
if previous_invoice is not None:
num = int(previous_invoice.invoice_id[5:])
else:
num = 1
self.invoice_id = '{year}-{num:03d}'.format(year=current_date, num=num)
super(Invoice, self).save(*args, **kwargs)

You can use a CharField for the id, setting it as primary key and generating it automatically upon saving.
Something similar to the following should work:
class Invoice(models.Model):
id = models.CharField(max_length=8, null=False, primary_key=True)
...
def save(self, *args, **kwargs):
if self.pk is None:
current_year = datetime.now().year
previous_invoice = Invoice.objects.filter(id__contains=str(year)).order_by('-id').first()
if previous_invoice is not None:
num = int(previous_invoice.id[5:])
else:
num = 1
self.pk = '{year}-{num:03d}'.format(year=current_year, num=num)
super(Invoice, self).save(*args, **kwargs)
EDIT: Add missing primary_key attribute in id field definition.

Related

DateField bigger than most recent record

I have this DB table:
models.py
class Payment(models.Model):
date = models.DateField(blank=False,verbose_name="Data")
description = models.CharField(max_length = 300,blank=False,verbose_name="Descrizione")
course_subscription = models.ForeignKey(CourseSubscription,null=True,on_delete=models.CASCADE,verbose_name="Sottoscrizione Corso")
payment_method = models.CharField(max_length = 4,blank=False,verbose_name="Modalità di pagamento",choices=PAYMENTS)
value = models.DecimalField(max_digits=7, decimal_places=2,blank=False,verbose_name="Importo")
receipt= models.FileField(blank=False,verbose_name="Ricevuta")
How can I add a constraint in model or form which allows the insertion of new payments only if the date is more recent than the most recent record? In other words, I want a new payment has always a more recent date than the last inserted one.
I tried with:
forms.py
class AddPaymentForm(forms.Form):
def __init__(self, max_value, min_date, *args, **kwargs):
super(AddPaymentForm, self).__init__(*args, **kwargs)
self.fields['value'] = forms.DecimalField(
max_digits=7,
decimal_places=2,
required=True,
label="Importo",
min_value=0.01,
max_value=max_value,
help_text= f"Valore Massimo: {max_value}",
widget = forms.NumberInput()
)
self.fields['date'] = forms.DateField(
required=True,
label="Data",
min_value=min_date,
help_text= f"Data minima: {min_date}",
widget=forms.TextInput(
attrs={'type': 'date'}
)
)
....
and
views.py
...
payments = Payment.objects.all().order_by('-date')
initial = {
'date': datetime.date.today().strftime("%Y-%m-%d"),
'member':member,
'description': description,
'value': 1.00,
'subscription_type' :subscription_type,
'subscription_id' :subscription_id
}
if len(payments)>0 :
min_date = payments[0].date.strftime("%Y-%m-%d")
initial["date"] = min_date
else:
min_date = None
form = AddPaymentForm(
initial= initial,
max_value = remaining_fee,
min_date = min_date
)
...
This way works with 'value' field, but forms.DateField doesn't have "min_value" attribute.
Solved adding clean method to AddPaymentForm:
class AddPaymentForm(forms.Form):
def __init__(self, max_value, *args, **kwargs):
super(AddPaymentForm, self).__init__(*args, **kwargs)
self.fields['value'] = forms.DecimalField(
max_digits=7,
decimal_places=2,
required=True,
label="Importo",
min_value=0.01,
max_value=max_value,
help_text= f"Valore Massimo: {max_value}",
widget = forms.NumberInput()
)
def clean(self, *args, **kwargs):
super(AddPaymentForm, self).clean(*args, **kwargs)
payments = Payment.objects.all().order_by('-date')
if len(payments)>0 :
min_date = payments[0].date
else:
min_date = datetime.date(2020,8,1)
form_date = self.cleaned_data.get("date")
if form_date < min_date:
self.add_error('date', f'La data deve essere più decente del {min_date.strftime("%d/%m/%Y")}')

Use sessions to count pageviews django detailview

I am trying to count the visits to a view. I would like for the counter to increment by 1 every time someone calls up the view. Then, I want the "visits" field on the model to automatically update with the latest count. However, I am not sure how to implement this. Using some code I've found, I am trying this:
models.py
class Statute(models.Model):
address = models.ForeignKey(Address,
null = True)
statute_name = models.CharField(max_length=25,
default='')
category = models.CharField(max_length=55,
default='')
section_number = models.CharField(max_length=55,
default='')
section_title = models.CharField(max_length=255,
default='')
timestamp = models.DateTimeField(editable=False)
visits = models.IntegerField(default=0)
content = models.TextField(default='')
slug = models.SlugField()
views.py
def get_context_data(self, **kwargs):
context = super(LibraryInStateView, self).get_context_data(**kwargs)
state = State.objects.get(slug=self.kwargs.get('state'))
statute = Statute.objects.all()
context['latest_statutes'] = statute.filter(
address__zipcode__city__county__state=state).order_by(
'-timestamp')
context['statute_count'] = Statute.objects.filter(
address__zipcode__city__county__state=state).count()
context['view_count'] = self.request.session['visits']+1
return context
You can include it in .get_object() method in LibraryInStateView
def get_object(self):
statute = super().get_object()
statute.visits += 1
statute.save()
self.view_count = statute.visits
return statute
Or get method:
def get(self, request, *args, **kwargs):
statute = # ... code to retrieve Statute for this view
statute.visits += 1
statute.save()
self.view_count = statute.visits
return super().get(request, *args, **kwargs)
Then once you attached view_count to class instance, you can add it to context:
def get_context_data(self, **kwargs):
...
context['view_count'] = self.view_count
return context
In your view update:
statute = Statute.objects.filter(address__zipcode__city__county__state=state)
statute.visits += 1
statute.save()
context['statute_count'] = statute

Django ValueError when trying to save ManyToMany Values from a Form

I get the error "" needs to have a value for field "dataset" before this many-to-many relationship can be used." when trying to assign values to a ManyToMany field in my views. I've looked at many related questions here on SO that say I must save my Dataset object first. I think I am doing that...what is going wrong?? My database already contains four Subject items.
models.py
class Subject(TimeStampedModel):
subject_type = models.CharField(max_length=128, blank=False)
def __unicode__(self):
return self.subject_type
class Dataset(TimeStampedModel):
dataset_id = models.CharField(max_length=256)
dataset_doi = models.CharField(max_length=15)
dataset_name = models.CharField(max_length=256, blank=False)
dataset_description = models.TextField(blank=False)
lab = models.CharField(max_length=256, blank=False)
biological_sample = models.CharField(max_length=256, blank=False)
subject_type = models.ManyToManyField('Subject', related_name='datasets', blank=True)
date_collected = models.DateField(blank=True)
collection_facility = models.ManyToManyField('CollectionFacility', related_name='datasets', blank=True)
processing_notes = models.TextField(blank=True)
release_date = models.DateField()
release_asap = models.BooleanField()
pdb_code = models.CharField(max_length=256, blank=True)
publication_link = models.URLField(blank=True)
def create_name(self):
self.dataset_name = "%s %s" % (self.biological_sample, self.lab)
def save(self, *args, **kwargs):
self.dataset_id = self.id
def __unicode__(self):
return "%s : %s" % (self.dataset_name, self.dataset_id)
forms.py RegistrationForm:
class RegistrationForm(forms.Form):
subject_type = forms.ModelMultipleChoiceField(
label="Subject",
queryset = Subject.objects.all(),
widget=forms.CheckboxSelectMultiple(),
required = True,
)
views.py
def create_registration(form):
dataset = Dataset()
dataset.DOI = "preUpload"
dataset.lab = form.cleaned_data['lab']
dataset.biological_sample = form.cleaned_data['sample']
dataset.resource_type = form.cleaned_data['dataset_type']
dataset.dataset_description = form.cleaned_data['dataset_description']
dataset.date_collected = form.cleaned_data['date_collected']
dataset.release_date = form.cleaned_data['release_date']
dataset.release_asap = form.cleaned_data['release_asap']
if form.cleaned_data['pdb_code']:
dataset.pdb_code = form.cleaned_data['pdb_code']
if form.cleaned_data['publication_link']:
dataset.publication_link = form.cleaned_data['publication_link']
dataset.create_name()
dataset.save() # I don't think this save is working?
subjects = form.cleaned_data['subject_type']
dataset.subject_type = [x for x in subjects]
for facility in form.cleaned_data['facility']
dataset.collection_facility.add(facility)
dataset.save()
return dataset
def registration_submit(request):
registration_form = RegistrationForm(request.POST)
if registration_form.is_valid():
registration = create_registration(registration_form)
.......
You forgot to call the original save() in the overriden Dataset.save() method.
def save(self, *args, **kwargs):
self.dataset_id = self.id
super(Dataset, self).save(*args, **kwargs)

correctly aggregating model objects and simple view calculation

if "allotted_pto" (paid time off) is an integer field (expressing number of days) in a UserProfile model:
class UserProfile(models.Model):
user = models.ForeignKey(User, unique=True)
fullname = models.CharField(max_length=64, unique=False)
company = models.CharField(max_length=50, choices=CLIENT_CHOICES)
...
allotted_pto = models.IntegerField(max_length=2, blank=True, null=True)
...
User.profile = property(lambda u: UserProfile.objects.get_or_create(user=u)[0])
and "total_days" returns an integer from a vacation request model:
class LeaveRequest(models.Model):
employee = models.ForeignKey(UserProfile)
supervisor = models.ForeignKey(UserProfile, related_name='+', blank=False, null=False)
...
total_days = models.IntegerField(max_length=2, blank=True, null=True)
def __unicode__ (self):
return u'%s %s' % (self.employee, self.submit_date)
def save(self, *args, **kwargs):
fromdate = self.start_date
todate = self.return_date
daygenerator = (fromdate + timedelta(x + 1) for x in xrange((todate - fromdate).days))
self.total_days = sum(1 for day in daygenerator if day.weekday() < 5)
super(LeaveRequest, self).save(*args, **kwargs)
...
how can I construct a view that gives me the sum of "total_days" from a filter set of records and subtract that sum from the "allotted_pto" in the user profile? The simple view I wrote (see below) produces the number of "total_days" objects (in dictionary form) as opposed to counting the actual days, and the request for "allotted_pto" is apparently incorrectly constructed because it returns nothing at all...
#views.py
def leave_screen(request, id):
profile = UserProfile.objects.get(user=id)
records = LeaveRequest.objects.filter(employee=id)
agg_pto = LeaveRequest.objects.aggregate(Count('total_days'))
if profile.allotted_pto: #if the allotted_pto field in UserProfile is not empty
allotted_pto = profile.allotted_pto
remaining_pto = allotted_pto - agg_pto
else:
remaining_pto = "na"
return render_to_response("vacation/leave_request.html", {'records': records, 'agg_pto': agg_pto, 'remaining_pto': remaining_pto})
ok, figured out calculation:
def leave_screen(request, id):
...
agg_pto = LeaveRequest.objects.filter(employee=id).aggregate(Sum('total_days'))
agg_pto = agg_pto['total_days__sum']
just have to figure out how to pull the allotted_pto integer from the User Profile model.
ok, so this wasn't as difficult as I thought. The first challenge was to get an aggregate sum of objects. My first attempt was close but I should have just used "Sum" as opposed to "Count":
agg_pto = LeaveRequest.objects.filter(employee=id).aggregate(Sum('total_days'))
then I just used the python method for extracting the value from a dictionary:
agg_pto = agg_pto['total_days__sum']
finally:
def leave_screen(request, id):
user = request.user.id
profile = request.user.get_profile()
records = LeaveRequest.objects.filter(employee=id).order_by('-submit_date')
agg_pto = LeaveRequest.objects.filter(employee=id).aggregate(Sum('total_days'))
agg_pto = agg_pto['total_days__sum']
allotted_pto = profile.allotted_pto
if allotted_pto: #if the allotted_pto field in UserProfile is not empty
remaining_pto = allotted_pto - agg_pto
else:
remaining_pto = "na"
supervised_records = LeaveRequest.objects.filter(supervisor=id).order_by('-submit_date')
return render_to_response("vacation/leave_request.html", {'records': records, 'supervised_records': supervised_records, 'agg_pto': agg_pto, 'allotted_pto': allotted_pto, 'remaining_pto': remaining_pto, 'profile': profile })
I don't know why it was so hard for me to figure out the syntax for pulling objects from the UserProfile. But I do know that the django-debug-toolbar is very helpful.

Subtract models.DateField to get number of days

I have a simple model that tracks work leave requests:
class LeaveRequest(models.Model):
employee = models.ForeignKey(UserProfile)
supervisor = models.ForeignKey(UserProfile, related_name='+', blank=False, null=False)
submit_date = models.DateField(("Date"), default=datetime.date.today)
leave_type = models.CharField(max_length=64, choices=TYPE_CHOICES)
start_date = models.DateField(("Date"))
return_date = models.DateField(("Date"))
total_days = models.IntegerField()
notes = models.TextField(max_length=1000)
def __unicode__ (self):
return u'%s %s' % (self.employee, self.submit_date)
class Admin:
pass
class Meta:
ordering = ['-submit_date']
In the view I need a function to calculate the number of days requested. Secondarily, I'll need a method to count only weekdays, but for now I've got the following:
def leave_screen(request, id):
records = LeaveRequest.objects.filter(employee=id)
total_days = LeaveRequest.return_date - LeaveRequest.start_date
tpl = 'vacation/leave_request.html'
return render_to_response(tpl, {'records': records })
which produces a attribute error
type object 'LeaveRequest' has no attribute 'return_date
any suggestions?
In total_days, you are calling the model and not the instance of that model - records - that you created.
If you want to view just a single Leave record, you would need to pass the id of the LeaveRequest
def leave_screen(request, id):
records = LeaveRequest.objects.get(id=id)
total_days = records.return_date - records.start_date
tpl = 'vacation/leave_request.html'
return render_to_response(tpl, {'records': records })
The answer that suggests using it as a property will work but I think I'll prefer keeping it as a field and just computing it at the time of insert.
class LeaveRequest(models.Model):
employee = models.ForeignKey(UserProfile)
supervisor = models.ForeignKey(UserProfile, related_name='+', blank=False, null=False)
submit_date = models.DateField(("Date"), default=datetime.date.today)
leave_type = models.CharField(max_length=64, choices=TYPE_CHOICES)
start_date = models.DateField(("Date"))
return_date = models.DateField(("Date"))
total_days = models.IntegerField()
notes = models.TextField(max_length=1000)
def __unicode__ (self):
return u'%s %s' % (self.employee, self.submit_date)
def save(self, *args, **kwargs):
self.total_days = (self.return_date - self.start_date).days
super(LeaveRequest, self).save(*args, **kwargs)
class Admin:
pass
class Meta:
ordering = ['-submit_date']
This way when you put in the logic for excluding weekends you are saving computation to calculate the days everytime at the time of listing all leave requests.
I wouldn't have 'total_days' as a field in the LeaveRequest class, but rather as a property.
class LeaveRequest(models.Model):
(other fields)
#property
def total_days(self):
oneday = datetime.timedelta(days=1)
dt = self.start_date
total_days = 0
while(dt <= self.return_date):
if not dt.isoweekday() in (6, 7):
total_days += 1
dt += oneday
return totaldays
# view function
def leave_screen(request, id):
# get leave request by id
leavereq = LeaveRequest.objects.get(id=id)
return render_to_response("vacation/leave_request.html", {"leavereq": leavereq})
# template code
...
<body>
{{ leavereq.total_days }}
</body>