I'm trying to restrict a bidder that bids on certain project via a form from bidding neither less than the minimum budget that the project publisher has specifies neither greater than the maximum budget. After checking similar problems i found that overriding the clean method is the most suitable, but i'm facing " 'Bid' object has no attribute 'kwargs' " error
This is the clean method:
def clean(self):
if Project.objects.get(pk=self.kwargs['pk']).budget_min >= self.bid_amount or self.bid_amount >= Project.objects.get(pk=self.kwargs['pk']).budget_max:
raise ValidationError('the bid amount should be between the minimum and maximum project budget')
This is my models.py
# projects/models.py
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import models
from django.urls import reverse
from django.core.validators import MaxValueValidator, MinLengthValidator, MinValueValidator
from upload_validator import FileTypeValidator
from .validators import user_directory_path, validate_file_size
from multiselectfield import MultiSelectField
class Project(models.Model):
title = models.CharField(
'Choose a title for your project',
max_length=255,
validators=[
MinLengthValidator(
15, 'Title must be descriptive and greater than 15 characters')
]
)
body = models.TextField('Tell us more about your project',
max_length=3000,
validators=[
MinLengthValidator(
10, 'Must be descriptive and greater than 10 characters')
]
)
upload_file = models.FileField(
upload_to=user_directory_path,
verbose_name="upload file:(Max file size: 2.5 MB)",
validators=[
validate_file_size,
FileTypeValidator(
allowed_types=[
'application/msword',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/pdf',
'application/vnd.ms-powerpoint',
'application/vnd.openxmlformats-officedocument.presentationml.presentation',
'application/vnd.ms-excel',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'image/tiff',
'image/jpeg',
'image/png'
],
allowed_extensions=['.doc', '.docx',
'.jpg', '.jpeg', '.png', '.pdf', '.zip']
)
],
null=True, blank=True
)
POSSIBLE_SKILLS = (
('Bash Scripting', 'Bash Scripting'), ('CSS', 'CSS'), ('Django', 'Django'),
('HTML', 'HTML'), ('JavaScript', 'JavaScript'), ('React', 'React'),
('Linux', 'Linux'), ('MongoDB', 'MongoDB'), ('NoSQL', 'NoSQL'),
('Powershell', 'Powershell'), ('Python', 'Python'),
('Shell Script', 'Shell Script')
)
skills = MultiSelectField(choices=POSSIBLE_SKILLS, default='')
PAYMENT_TYPE = (
('Fixed', 'Fixed'),
('Hourly', 'Hourly'),
)
payments = models.CharField(
'How do you want to pay?',
max_length=20, choices=PAYMENT_TYPE, default="Fixed")
budget_min = models.PositiveIntegerField(
'What is your estimated Minimum budget?',
default=10, validators=[MinValueValidator(1), MaxValueValidator(100000)])
budget_max = models.PositiveIntegerField(
'What is your estimated Maximum budget? :',
default=100, validators=[MinValueValidator(1), MaxValueValidator(100000)])
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# author is a FOREIGN KEY field in Project table, that refers to the PRIMARY KEY in CustomUser table
# the on_delete method used to tell django when deleting the author delete he's projects
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
# Shows up in the admin list
def __str__(self):
return self.title
def get_absolute_url(self):
"""[summary: where to go when a Project object is created.
]
Returns:
[description: this method should appear to return a string that can be used to refer
to the object over HTTP.]
"""
return reverse('project_detail', args=[str(self.id)])
class Bid(models.Model):
bid_amount = models.PositiveIntegerField(
default=10, validators=[MinValueValidator(1), MaxValueValidator(1000000)]
)
delivery_in = models.PositiveIntegerField(
default=1, validators=[MinValueValidator(1), MaxValueValidator(10000)]
)
describe_your_proposal = models.TextField(
max_length=144,
validators=[
MinLengthValidator(
20, "bid must be descriptive and greater than 20 characters")
]
)
STATUS_TYPE = (
('Approved', 'Approved'),
('Closed', 'Closed'),
('Pending', 'Pending'),
)
status = models.CharField(
'How do you want to pay?',
max_length=20, choices=STATUS_TYPE, default="Pending")
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
# project is a FOREIGN KEY field in Bid table, that refers to
# the PRIMARY KEY in Project table using related name project.bids
# in templates to access bids of the project
# the on_delete method used to tell django when deleting the project delete it's bids
project = models.ForeignKey(
Project,
on_delete=models.CASCADE,
related_name='bids',
)
# author is a FOREIGN KEY field in Bid table, that refers to
# the PRIMARY KEY in CustomUser table
author = models.ForeignKey(
get_user_model(),
on_delete=models.CASCADE,
)
#
def clean(self):
if Project.objects.get(pk=self.kwargs['pk']).budget_min >= self.bid_amount or self.bid_amount >= Project.objects.get(pk=self.kwargs['pk']).budget_max:
raise ValidationError('the bid amount should be between the minimum and maximum project budget')
# Shows up in the admin list
def __str__(self):
return self.describe_your_proposal
def get_absolute_url(self):
"""[summary: where to go when a Bid object is created.
]
Returns:
[description: this method should appear to return a string that can be used
to refer to the object over HTTP.]
"""
return reverse('bid_detail', args=[str(self.id)])
If the project referenced in the clean method is the bid.project object, then you can just call it directly.
def clean(self):
if not self.project.budget_min <= self.bid_amount <= self.project.budget_max:
raise ValidationError('The bid amount should be between the minimum and maximum project budget.')
I'd also recommend switching up the check if the bid is in between the min and max values, I think it is easier to read.
Related
I'm trying to send an email notification when an Article instance is created or modified. I use signals and send_mail. Everything works great while I just create and modify articles. But, if I delete an Article, I've got a notification that it was updated! This is incorrect behavior. What can be a reason (and solution)?
models.py
from django.core.mail import send_mail
from django.db.models.signals import post_save
from django.dispatch import receiver
# ...
class Article(TimeStampedModel):
title = models.CharField(_('Title'), max_length=255, db_index=True)
slug = AutoSlugField(_('Slug'), unique_with=['year', 'month'], populate_from="title", db_index=True)
picture = models.ImageField(verbose_name=_('Image'),
upload_to = upload_to)
category = models.ForeignKey('Category', blank=True, null=True,
on_delete=models.SET_NULL, db_index=True, related_name='articles')
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
annotation = models.TextField(_('Brief annotation'), blank=True) # Brief annotation (1 paragraph)
date = models.DateTimeField(_('Date'), default=timezone.now, db_index=True)
modified = models.DateTimeField(_('Modified'), db_index=True, auto_now=True)
year = models.PositiveIntegerField(_('Year'), db_index=True, blank=True)
month = models.CharField(_('Month'), db_index=True, max_length=2, blank=True)
is_published = models.BooleanField(_('Is published'), default=False)
def delete(self, *args, **kwargs): # Remove the media files of the article together with their folder
from django.core.files.storage import default_storage
if self.picture:
with contextlib.suppress(FileNotFoundError):
default_storage.delete( self.picture_thumbnail.path )
self.picture.delete()
path = os.path.join(settings.MEDIA_ROOT, settings.CKEDITOR_UPLOAD_PATH, 'news', str(self.year), str(self.month), str(self.slug))
if os.path.isdir(path):
shutil.rmtree(path) # Remove folder
super().delete(*args, **kwargs)
def __str__(self):
# Потом допилить, чтобы год и месяц тоже выводился
return self.title # There can be other ways: year, month and slugname, for example
def get_absolute_url(self):
return reverse('news:detail', kwargs={'year': self.year, 'month': self.month, 'slug':self.slug})
class Meta:
unique_together = ("year", "month", "slug")
ordering = ['-date', '-modified',]
class Comment(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE, related_name='comments')
author = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL)
text = RichTextField(_('Comment'), config_name = 'tiny')
active = models.BooleanField(_('Visible'), default=True, db_index=True)
created = models.DateTimeField(_('Created at'), auto_now_add=True, db_index=True)
updated = models.DateTimeField(_('Updated at'),auto_now=True)
def __str__(self):
return f'Comment by {self.author} on {self.article}'
class Meta:
#verbose_name_plural = _('Comments')
#verbose_name = _('Comment')
ordering = ['-created']
#receiver(post_save, sender=Article)
def email_on_article_change(sender, instance, created, **kwargs):
# if a new officer is created, compose and send the email
if created: # created -- это булевская переменная, чтобы отличать новые записи от изменений старых
action = "created"
else:
action = "updated"
title = instance.title if instance.title else "no title given"
annotation = instance.annotation if instance.annotation else "no annotation given"
author = instance.author if instance.author else 'no author assignment'
subject = 'TITLE: {0}, ANNOTATION {1}, AUTHOR: {2}'.format(title, annotation, author)
message = 'A New Article has been ' + action + '!\n'
message += 'TITLE: ' + str(title) + '\n' + 'ANNOTATION: ' + str(annotation) + '\n' + 'AUTHOR: ' + str(author) + '\n'
message += '--' * 30
send_mail(
subject,
message,
'mail#raptors.ru',
['michael_romanov#inbox.ru', 'romanov-spm#yandex.ru'],
fail_silently=False,
)
P. S. I've read on this forum that sometimes this behavior can be caused by foreign keys. My Article model has two foreign keys, author and category. Also, there is a Comment model which has a foreign key 'article'. But I can't see how they can cause this problem and how to solve it. Please help me anybody!
I can't make file upload form field work. My references are:
https://github.com/lb-/bakerydemo/blob/stack-overflow/61289214-wagtail-form-file-upload/bakerydemo/base/models.py
https://dev.to/lb/image-uploads-in-wagtail-forms-39pl
The field was added to admin and appear correctly on my site, but when I try to send the form:
if the field is required, the form is reloaded without any error
if the field is not required, the form is sent but the file is not uploaded
What am I doing wrong? I've been working on this for 3 days and can't find any error message pointing what's wrong. When I get some error message it's always too generic.
Please help! :)
My models.py inside bakerydemo/bakerydemo/base
from __future__ import unicode_literals
import json
from os.path import splitext
from django.core.serializers.json import DjangoJSONEncoder
from django.db import models
from django.utils.html import format_html
from django.urls import reverse
from django import forms
from modelcluster.fields import ParentalKey
from modelcluster.models import ClusterableModel
from wagtail.admin.edit_handlers import (
FieldPanel,
FieldRowPanel,
InlinePanel,
MultiFieldPanel,
PageChooserPanel,
StreamFieldPanel,
)
from wagtail.core.fields import RichTextField, StreamField
from wagtail.core.models import Collection, Page
from wagtail.contrib.forms.forms import FormBuilder
from wagtail.contrib.forms.models import AbstractEmailForm, AbstractFormSubmission, AbstractFormField, FORM_FIELD_CHOICES
from wagtail.contrib.forms.views import SubmissionsListView
from wagtail.images import get_image_model
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.images.fields import WagtailImageField
from wagtail.search import index
from wagtail.snippets.models import register_snippet
from .blocks import BaseStreamBlock
from django.core.validators import ValidationError
import logging
#register_snippet
class People(index.Indexed, ClusterableModel):
"""
A Django model to store People objects.
It uses the `#register_snippet` decorator to allow it to be accessible
via the Snippets UI (e.g. /admin/snippets/base/people/)
`People` uses the `ClusterableModel`, which allows the relationship with
another model to be stored locally to the 'parent' model (e.g. a PageModel)
until the parent is explicitly saved. This allows the editor to use the
'Preview' button, to preview the content, without saving the relationships
to the database.
https://github.com/wagtail/django-modelcluster
"""
first_name = models.CharField("First name", max_length=254)
last_name = models.CharField("Last name", max_length=254)
job_title = models.CharField("Job title", max_length=254)
image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
panels = [
MultiFieldPanel([
FieldRowPanel([
FieldPanel('first_name', classname="col6"),
FieldPanel('last_name', classname="col6"),
])
], "Name"),
FieldPanel('job_title'),
ImageChooserPanel('image')
]
search_fields = [
index.SearchField('first_name'),
index.SearchField('last_name'),
]
#property
def thumb_image(self):
# Returns an empty string if there is no profile pic or the rendition
# file can't be found.
try:
return self.image.get_rendition('fill-50x50').img_tag()
except: # noqa: E722 FIXME: remove bare 'except:'
return ''
def __str__(self):
return '{} {}'.format(self.first_name, self.last_name)
class Meta:
verbose_name = 'Person'
verbose_name_plural = 'People'
#register_snippet
class FooterText(models.Model):
"""
This provides editable text for the site footer. Again it uses the decorator
`register_snippet` to allow it to be accessible via the admin. It is made
accessible on the template via a template tag defined in base/templatetags/
navigation_tags.py
"""
body = RichTextField()
panels = [
FieldPanel('body'),
]
def __str__(self):
return "Footer text"
class Meta:
verbose_name_plural = 'Footer Text'
class StandardPage(Page):
"""
A generic content page. On this demo site we use it for an about page but
it could be used for any type of page content that only needs a title,
image, introduction and body field
"""
introduction = models.TextField(
help_text='Text to describe the page',
blank=True)
image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
help_text='Landscape mode only; horizontal width between 1000px and 3000px.'
)
body = StreamField(
BaseStreamBlock(), verbose_name="Page body", blank=True
)
content_panels = Page.content_panels + [
FieldPanel('introduction', classname="full"),
StreamFieldPanel('body'),
ImageChooserPanel('image'),
]
class GalleryPage(Page):
"""
This is a page to list locations from the selected Collection. We use a Q
object to list any Collection created (/admin/collections/) even if they
contain no items. In this demo we use it for a GalleryPage,
and is intended to show the extensibility of this aspect of Wagtail
"""
introduction = models.TextField(
help_text='Text to describe the page',
blank=True)
image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
help_text='Landscape mode only; horizontal width between 1000px and '
'3000px.'
)
body = StreamField(
BaseStreamBlock(), verbose_name="Page body", blank=True
)
collection = models.ForeignKey(
Collection,
limit_choices_to=~models.Q(name__in=['Root']),
null=True,
blank=True,
on_delete=models.SET_NULL,
help_text='Select the image collection for this gallery.'
)
content_panels = Page.content_panels + [
FieldPanel('introduction', classname="full"),
StreamFieldPanel('body'),
ImageChooserPanel('image'),
FieldPanel('collection'),
]
# Defining what content type can sit under the parent. Since it's a blank
# array no subpage can be added
subpage_types = []
class HomePage(Page):
"""
The Home Page. This looks slightly more complicated than it is. You can
see if you visit your site and edit the homepage that it is split between
a:
- Hero area
- Body area
- A promotional area
- Moveable featured site sections
"""
# Hero section of HomePage
image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
help_text='Homepage image'
)
hero_text = models.CharField(
max_length=255,
help_text='Write an introduction for the bakery'
)
hero_cta = models.CharField(
verbose_name='Hero CTA',
max_length=255,
help_text='Text to display on Call to Action'
)
hero_cta_link = models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
verbose_name='Hero CTA link',
help_text='Choose a page to link to for the Call to Action'
)
# Body section of the HomePage
body = StreamField(
BaseStreamBlock(), verbose_name="Home content block", blank=True
)
# Promo section of the HomePage
promo_image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
help_text='Promo image'
)
promo_title = models.CharField(
null=True,
blank=True,
max_length=255,
help_text='Title to display above the promo copy'
)
promo_text = RichTextField(
null=True,
blank=True,
help_text='Write some promotional copy'
)
# Featured sections on the HomePage
# You will see on templates/base/home_page.html that these are treated
# in different ways, and displayed in different areas of the page.
# Each list their children items that we access via the children function
# that we define on the individual Page models e.g. BlogIndexPage
featured_section_1_title = models.CharField(
null=True,
blank=True,
max_length=255,
help_text='Title to display above the promo copy'
)
featured_section_1 = models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
help_text='First featured section for the homepage. Will display up to '
'three child items.',
verbose_name='Featured section 1'
)
featured_section_2_title = models.CharField(
null=True,
blank=True,
max_length=255,
help_text='Title to display above the promo copy'
)
featured_section_2 = models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
help_text='Second featured section for the homepage. Will display up to '
'three child items.',
verbose_name='Featured section 2'
)
featured_section_3_title = models.CharField(
null=True,
blank=True,
max_length=255,
help_text='Title to display above the promo copy'
)
featured_section_3 = models.ForeignKey(
'wagtailcore.Page',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+',
help_text='Third featured section for the homepage. Will display up to '
'six child items.',
verbose_name='Featured section 3'
)
content_panels = Page.content_panels + [
MultiFieldPanel([
ImageChooserPanel('image'),
FieldPanel('hero_text', classname="full"),
MultiFieldPanel([
FieldPanel('hero_cta'),
PageChooserPanel('hero_cta_link'),
]),
], heading="Hero section"),
MultiFieldPanel([
ImageChooserPanel('promo_image'),
FieldPanel('promo_title'),
FieldPanel('promo_text'),
], heading="Promo section"),
StreamFieldPanel('body'),
MultiFieldPanel([
MultiFieldPanel([
FieldPanel('featured_section_1_title'),
PageChooserPanel('featured_section_1'),
]),
MultiFieldPanel([
FieldPanel('featured_section_2_title'),
PageChooserPanel('featured_section_2'),
]),
MultiFieldPanel([
FieldPanel('featured_section_3_title'),
PageChooserPanel('featured_section_3'),
]),
], heading="Featured homepage sections", classname="collapsible")
]
def __str__(self):
return self.title
class FormField(AbstractFormField):
logging.warning('FormField')
page = ParentalKey('FormPage', related_name='form_fields', on_delete=models.CASCADE)
field_type = models.CharField(
verbose_name='field type',
max_length=16,
choices=FORM_FIELD_CHOICES + (('fileupload', 'File Upload'),)
)
class CustomFormBuilder(FormBuilder):
logging.warning('CustomFormBuilder')
def create_fileupload_field(self, field, options):
return forms.FileField(**options)
class CustomSubmissionsListView(SubmissionsListView):
"""
further customisation of submission list can be done here
"""
logging.warning('CustomSubmissionsListView')
pass
class CustomFormSubmission(AbstractFormSubmission):
# important - adding this custom model will make existing submissions unavailable
# can be resolved with a custom migration
def get_data(self):
"""
Here we hook in to the data representation that the form submission returns
Note: there is another way to do this with a custom SubmissionsListView
However, this gives a bit more granular control
"""
logging.warning('CustomFormSubmission')
file_form_fields = [
field.clean_name for field in self.page.specific.get_form_fields()
if field.field_type == 'fileupload'
]
data = super().get_data()
for field_name, field_vale in data.items():
if field_name in file_form_fields:
# now we can update the 'representation' of this value
# we could query the FormUploadedFile based on field_vale (pk)
# then return the filename etc.
pass
return data
def upload_validator(value):
raise ValidationError(value)
class FormUploadedFile(models.Model):
logging.warning('FormUploadedFile')
file = models.FileField(upload_to="files/%Y/%m/%d", validators=[upload_validator])
# file = models.FileField(upload_to="files/%Y/%m/%d")
field_name = models.CharField(blank=True, max_length=254)
class FormPage(AbstractEmailForm):
form_builder = CustomFormBuilder
submissions_list_view_class = CustomSubmissionsListView
logging.warning('FormPage')
image = models.ForeignKey(
'wagtailimages.Image',
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='+'
)
# body = RichTextField(default="")
# body = StreamField(BaseStreamBlock())
body = StreamField(
BaseStreamBlock(), verbose_name="Page body", blank=True
)
thank_you_text = RichTextField(blank=True)
# Note how we include the FormField object via an InlinePanel using the
# related_name value
content_panels = AbstractEmailForm.content_panels + [
ImageChooserPanel('image'),
StreamFieldPanel('body'),
InlinePanel('form_fields', label="Form fields"),
FieldPanel('thank_you_text', classname="full"),
MultiFieldPanel([
FieldRowPanel([
FieldPanel('from_address', classname="col6"),
FieldPanel('to_address', classname="col6"),
]),
FieldPanel('subject'),
], "Email"),
]
def get_submission_class(self):
"""
Returns submission class.
Important: will make your existing data no longer visible, only needed if you want to customise
the get_data call on the form submission class, but might come in handy if you do it early
You can override this method to provide custom submission class.
Your class must be inherited from AbstractFormSubmission.
"""
# print('get_submission_class')
return CustomFormSubmission
def process_form_submission(self, form):
"""
Accepts form instance with submitted data, user and page.
Creates submission instance.
You can override this method if you want to have custom creation logic.
For example, if you want to save reference to a user.
"""
# print('process_form_submission')
file_form_fields = [field.clean_name for field in self.get_form_fields() if field.field_type == 'fileupload']
for (field_name, field_value) in form.cleaned_data.items():
if field_name in file_form_fields:
uploaded_file = FormUploadedFile.objects.create(
file=field_value,
field_name=field_name
)
# store a reference to the pk (as this can be converted to JSON)
form.cleaned_data[field_name] = uploaded_file.pk
return self.get_submission_class().objects.create(
form_data=json.dumps(form.cleaned_data, cls=DjangoJSONEncoder),
page=self,
)
I need some help in saving some records to the DB. Work for a parking app and i want to extend the way how unique_together queries the DB, by having an extra param. Just to understand what my situation is.
E.g i give the user possibility of booking a plot on (parking_on) 25th from 9-18 the location P1. Because the way both of the filters are uniques to the DB, someone else can't book the same plot P1 on the same dat 25th but from 19-24 ==>therefore i need some kind of validation here or manually change the way how the unique_together validation works.
Can someone please help?
Please find below my models and admin
from django.db import models
from django.conf import settings
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from datetime import datetime, timedelta, time
from django.core.exceptions import NON_FIELD_ERRORS
today = datetime.now().date()
tomorrow = today + timedelta(1)
now = datetime.now()
l = now.hour
m = int(now.strftime("%H"))
class ParcareManager(models.Manager):
def active(self, *args, **kwargs):
return super(ParcareManager, self).filter(draft=False).filter(parking_on__lte=datetime.now())
class Parcare(models.Model):
PARKING_PLOT = (('P1', 'Parking #1'),
('P2', 'Parking #2'), ('P3', 'Parking #3'))
user = models.ForeignKey(settings.AUTH_USER_MODEL,
blank=True, null=True, default=1, on_delete=True)
email = models.EmailField(blank=True, null=True)
parking_on = models.DateField(auto_now=False, auto_now_add=False, blank=True, null=True,
help_text='Alege data cand doresti sa vii in office',)
parking_off = models.DateField(auto_now=False, auto_now_add=False, blank=True, null=True,
help_text='Alege Data Plecarii')
numar_masina = models.CharField(max_length=8, default="IF77WXV", blank=True, null=True,
help_text='Introdu Numarul Masinii')
location = models.CharField(max_length=3, blank=True, default="P1", null=True, choices=PARKING_PLOT,
help_text='Alege Locul de Parcare Dorit')
updated = models.DateTimeField(
auto_now=True, auto_now_add=False, blank=True, null=True)
timestamp = models.DateTimeField(
auto_now=False, auto_now_add=True, blank=True, null=True)
venire = models.TimeField(default=time(
9, 00), auto_now=False, auto_now_add=False, help_text='Alege Ora Venirii')
plecare = models.TimeField(default=time(
18, 00), auto_now=False, auto_now_add=False, help_text='Alege Ora Plecarii')
objects = ParcareManager()
def __str__(self):
return self.location + " | " + str(self.parking_on) + " | " + str(self.parking_off)
class Meta:
verbose_name_plural = "parcare"
ordering = ["-parking_on"]
unique_together = ("parking_on", "location")
def clean(self):
if self.parking_on == today: # merge--vedem dak parcam azi
raise ValidationError(
{'parking_on': _('Please book for a date starting tomorrow')})
if self.parking_off < self.parking_on: # merge-vedem daca bookam in trecut
raise ValidationError(
{'parking_off': _('You cant book for a past date!')})
def save(self):
list = []
d = self.parking_on
while d <= self.parking_off:
list.append(
Parcare(user=self.user,
email=self.email,
parking_on=d,
parking_off=d,
location=self.location
)
)
d = d + timedelta(days=1)
Parcare.objects.bulk_create(list)
from django.contrib import admin
from .models import Parcare
class ParcareModelAdmin(admin.ModelAdmin):
list_display = ["user", "location",
"parking_on", "parking_off", "venire", "plecare", "timestamp"]
list_display_links = ["user", "location"]
list_editable = ["parking_off", "parking_on", "venire", "plecare"]
list_filter = ["parking_on", "location", "email"]
search_fields = ["location", "parking_on"]
date_hierarchy = 'parking_on'
class Meta:
model = Parcare
def get_form(self, request, obj=None, **kwargs):
form = super().get_form(request, obj, **kwargs)
if not obj:
user = request.user
form.base_fields['user'].initial = user
form.base_fields['email'].initial = user.email
return form
admin.site.register(Parcare, ParcareModelAdmin)
Thank you in advance!
I just have some thoughts about this problem.
I think the best solution is replace DateField on DateTimeField. Then you can use in clean method something like this (just like an example with bad performance):
def clean(self):
qs = Parcare.objects.filter(location=self.location)
.filter(parking_on__gt=self.parking_on)
.filter(parking_off__lt=self.parking_on)
qs2 = Parcare.objects.filter(location=self.location)
.filter(parking_on__gt=self.parking_off)
.filter(parking_off__lt=self.parking_off)
if len(qs) > 0 or len(qs2) > 0:
raise ValidationError('Time is booked already')
if self.parking_on < datetime.now(): # merge--vedem dak parcam azi
raise ValidationError(
{'parking_on': _('Please book for a time more than now')})
if self.parking_off < self.parking_on: # merge-vedem daca bookam in trecut
raise ValidationError(
{'parking_off': _('You cant book for a past date!')})
(first of all sorry for my bad English)
I need to know if i can limit the number of objects that a user can create in a specific model, taking the number of object admitted to create from the user profile.
I go to try to explain this. i have this model
class StoreBranchOffice(models.Model):
store = models.ForeignKey(
StoreBranchOffice,
verbose_name=_('store branch')
)
name = models.CharField(
max_length=30,
verbose_name=_('name'),
)
email = models.EmailField(
blank=True,
verbose_name=_('email'),
)
phone = models.CharField(
max_length=15,
verbose_name=_('phone'),
)
address = models.CharField(
max_length=100,
verbose_name=_('address'),
)
User Profile Model
class UserProfile(models.Model):
# Relations
user = models.OneToOneField(
User,
verbose_name=_('User'),
)
store_branch = models.ForeignKey(
Store,
verbose_name=_('Store'),
)
# Attributes - Mandatory
level = models.IntegerField(
choices=LEVEL_CHOICES,
default=1,
verbose_name=_('Level'),
)
stores_enable = models.BooleanField(
default=True,
verbose_name=_('Stores Enable'),
)
branch_max = models.IntegerField(
default=1,
verbose_name=_('branches admited')
)
Well, in the userprofile, i have a field that have the number of branch office admitted for the user...
Well i need to check if the user try to create a branch office, if have admitted create branch office or if the user already have the maximum of branch offices admitted!
Thanks very much!!!
You could do it in views like this,
def your_view(request):
if form.is_valid():
store = request.user.store_branch
max_count = request.user.branch_max
if StoreBranchOffice.objects.filter(store=store).count() >= max_count:
return #Your error message
Or you could do this in your form validation,
class StoreBranchOfficeForm(forms.ModelForm):
def __init__(self, user, *args, **kwargs):
self.user = user
super(StoreBranchOfficeForm, self).__init__(*args, **kwargs)
class Meta:
model = StoreBranchOffice
fields = '__all__'
def clean(self):
store_id = self.cleaned_data.get('store')
store = Store.objects.get(id=store_id)
if not store == self.user.userprofile.store:
raise ValidationError("Not Authorised")
max_count = self.user.branch_max
if StoreBranchOffice.objects.filter(store=store).count() >= max_count:
raise ValidationError("Maximum count of offices reached.")
return super(StoreBranchOfficeForm, self).clean()
Then it will be validated when you call form.is_valid() in your view everytime..
But you need to pass user into the form each time,
form = StoreBranchOfficeForm(user=request.user)
I'm working on project managment tool for improve my django-skills.
I have a problem with contenttypes.
I have next models:
Project
Ticket - has ForeignKey to Project
Discussion - has ForeignKey to Project
Comment - has ForeignKey to Discussion
UserProfile - extension of django User
ProfileWatch - Projects or Discussions which users watch, contenttype use here
ProfileActions - contains users actions (add comment to discussion, start discussion, add ticket) contenttype also use here. Records in this table creating by signals from Ticket, Discussion and Comment
User recive notification about things that he watch(somebody leave new comment or start discussion or add ticket).
In view I get all notification for current user.
I know wich objects(Project, Discussion) have user actions but I don't know which object trigger this UserAction(Ticket, Comment, Discussion).
Ideally in template I need something like this (in brackets fields of ProfileAction model):
13:55 19.11.2012(action_date) admin(profile) add ticket(action_type) deploy this(?) to JustTestProject(content_object)
But now I have this:
13:55 19.11.2012(action_date) admin(profile) add ticket(action_type) to JustTestProject(content_object)
Any ideas about how organize models for store trigger objects?
Thanks for any help
View:
from django.views.generic import ListView
from app.models import ProfileAction
class ActionsList(ListView):
context_object_name = "actions"
template_name = 'index.html'
def get_queryset(self):
profile = self.request.user.get_profile()
where = ['(content_type_id={0} AND object_id={1})'.format(\
x.content_type_id,\
x.object_id\
) for x in profile.profilewatch_set.all()\
]
recent_actions = ProfileAction.objects.extra(
where=[
' OR '.join(where),
'profile_id={0}'.format(profile.pk)
],
order_by=['-action_date']
)
return recent_actions
Models:
#models
from django.contrib.auth.models import User
from django.contrib.contenttypes.models import ContentType
from django.contrib.contenttypes import generic
from django.db import models
from django.db.models.signals import post_save
from django.dispatch import receiver
from django.utils.translation import ugettext_lazy as _
class UserProfile(models.Model):
user = models.OneToOneField(User, verbose_name=_("Django user"))
first_name = models.CharField(_("First name"), blank=True, max_length=64, null=True)
last_name = models.CharField(_("Last name"), blank=True, max_length=64, null=True)
info = models.TextField(_("Additional information"), null=True)
phone = models.CharField(verbose_name=_("Phone"), max_length=15, blank=True, null=True)
class ProfileWatch(models.Model):
profile = models.ForeignKey(to=UserProfile, verbose_name=_(u"User profile"))
start_date = models.DateTimeField(verbose_name=_(u"Start date"), auto_now_add=True)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class ProfileAction(models.Model):
ACTION_TYPE_CHOICES = (
(0, _(u"Add comment")),
(1, _(u"Add discussion")),
(2, _(u"Add ticket")),
)
profile = models.ForeignKey(to=UserProfile, verbose_name=_(u"User profile"))
action_date = models.DateTimeField(verbose_name=_(u"Start date"), auto_now_add=True)
action_type = models.PositiveSmallIntegerField(
verbose_name=_("Status"),
choices=ACTION_TYPE_CHOICES
)
content_type = models.ForeignKey(ContentType)
object_id = models.PositiveIntegerField()
content_object = generic.GenericForeignKey('content_type', 'object_id')
class Project(models.Model):
title = models.CharField(_("Project title"), max_length=128)
description = models.TextField(_("Project description"), blank=True, null=True)
members = models.ManyToManyField(UserProfile, through='Participation', verbose_name=_("Members"), blank=True, null=True)
actions = generic.GenericRelation(ProfileAction)
class Ticket(models.Model):
title = models.CharField(_("Title"), max_length=256)
project = models.ForeignKey('Project', verbose_name=_("Project"))
description = models.TextField(_("Ticket description"), blank=True, null=True)
creator = models.ForeignKey(UserProfile, verbose_name=_("Creator"), related_name='created_tickets')
#receiver(post_save, sender=Ticket)
def add_action_for_ticket(sender, instance, created, **kwargs):
if created:
ProfileAction.objects.create(
profile=instance.creator,
action_type=2,
content_object=instance.project
)
class Discussion(models.Model):
project = models.ForeignKey(
to=Project,
verbose_name=_(u"Project"),
)
creator = models.ForeignKey(
to=UserProfile,
verbose_name=_(u"Creator"),
)
updated = models.DateTimeField(
verbose_name=_("Last update"),
auto_now=True
)
title = models.CharField(
verbose_name=_(u"title"),
max_length=120
)
actions = generic.GenericRelation(ProfileAction)
#receiver(post_save, sender=Discussion)
def add_action_for_discussion(sender, instance, created, **kwargs):
if created:
ProfileAction.objects.create(
profile=instance.creator,
action_type=1,
content_object=instance.project
)
class Comment(models.Model):
discussion = models.ForeignKey(
to=Discussion,
verbose_name=_(u"Discussion")
)
creator = models.ForeignKey(
to=UserProfile,
verbose_name=_(u"Creator"),
)
pub_date = models.DateTimeField(
verbose_name=_("Publication date"),
auto_now_add=True
)
text = models.TextField(
verbose_name=_("Comment text")
)
#receiver(post_save, sender=Comment)
def add_action_for_comment(sender, instance, created, **kwargs):
if created:
ProfileAction.objects.create(
profile=instance.creator,
action_type=0,
content_object=instance.discussion
)
I solved the problem. Just add next adtitional fileds to ProfileAction model and change signals handlers.
trigger_content_type = models.ForeignKey(ContentType, related_name='triggers')
trigger_id = models.PositiveIntegerField()
trigger_object = generic.GenericForeignKey('trigger_content_type', 'trigger_id')