How can i change a field based on another m2m field? - python

So, what i'm tryna do here is set the status of an object based on the length of m2m field.
Here's how it looks
from django.db import models
class Dependency(models.Model):
dependency = models.SlugField('Шаблон')
class Seo(models.Model):
statuses = (
(1, 'Дефолтный'),
(2, 'Дополнительный')
)
dependencies = models.ManyToManyField(
Dependency,
verbose_name='Зависимости',
blank=True,
help_text='Оставьте пустым, если это дефолтный шаблон'
)
h1 = models.CharField('Заголовок(h1)', max_length=200)
title = models.CharField('Заголовок(title)', max_length=200)
description = models.CharField('Описание', max_length=200)
keywords = models.TextField('Ключевые слова')
status = models.IntegerField('Статус', choices=statuses, blank=True, editable=False)
def save(self, *args, **kwargs):
if len(self.dependencies) == 0:
self.status = 1
else:
self.status = 2
# self.status = 1
#
# print(len(self.dependencies))
super().save(*args, **kwargs)
class Page(models.Model):
pass
But it throws me an error that goes like
ValueError: "<Seo: Seo object (None)>" needs to have a value for field "id" before this many-to-many relationship can be used.
And what it want to achieve is whenever the dependency field is empty then status should be 1 and otherwise it should be 2. But i couldn't find a way to do it.

I think you can use Django database transactions.
from django.db import transaction
class Seo(models.Model):
...
def save(self, *args, **kwargs):
instance = super(Seo, self).save(*args, **kwargs)
if self.id == None:
transaction.on_commit(self.update_status)
return instance
def update_status(self):
if len(self.dependencies) == 0:
self.status = 1
else:
self.status = 2
self.save()
Hope it could help.

So, after several hours googling and just melt-functioning i had a thought that just popped right into my brain, what if i google something like "django m2m validation" and i got it. after reading django docs and this Django ManyToMany model validation i managed to get it working.
but still for me it is indeed crazy why django has no m2m sort of saving and validation kinda thing by default.
Here goes the code.
admin.py
#admin.register(Seo)
class SeoAdmin(admin.ModelAdmin):
form = SEOForm
list_display = [
field.name for field in Seo._meta.get_fields() if field.name not in ['dependencies']
] + ['get_dependencies']
list_display_links = ['id']
search_fields = ['id', 'h1', 'title']
def get_dependencies(self, obj):
if obj:
return ', '.join(d.dependency for d in obj.dependencies.all())
get_dependencies.short_description = 'Зависимости'
forms.py
from django import forms
from django.core.exceptions import ValidationError
from .models import Seo
from .services import SEOService
_seo_service = SEOService()
class SEOForm(forms.ModelForm):
"""
I just wonder why Django doesn't have validation m2m mechanism.
"""
class Meta:
model = Seo
fields = [field.name for field in Seo._meta.get_fields()]
_delimiter = _seo_service.delimiter
def clean(self):
dependencies = self.cleaned_data.get('dependencies')
if dependencies:
fields = [
self.cleaned_data.get('h1'),
self.cleaned_data.get('title'),
self.cleaned_data.get('keywords'),
self.cleaned_data.get('description')
]
dependencies_list = [dep.dependency for dep in dependencies]
for field in fields:
if not _seo_service.is_value_valid(field, dependencies_list, delimiter=self._delimiter):
raise ValidationError(
f'The amount of {self._delimiter} signs is the same as dependencies. '
f'Amount of {self._delimiter}: {field.count(self._delimiter)}. Dependencies: {len(dependencies_list)}. '
f'Field content: {field}.'
)
return self.cleaned_data
def save(self, commit=True):
m = super(SEOForm, self).save(commit=False)
m.save()
self.save_m2m()
if not m.dependencies.all():
m.status = 1
return m
m.status = 2
dependencies_list = [dep.dependency for dep in m.dependencies.all()]
m.h1 = _seo_service.replace_by_delimiter(m.h1, dependencies_list, self._delimiter)
m.title = _seo_service.replace_by_delimiter(m.title, dependencies_list, self._delimiter)
m.description = _seo_service.replace_by_delimiter(m.description, dependencies_list, self._delimiter)
m.keywords = _seo_service.replace_by_delimiter(m.keywords, dependencies_list, self._delimiter)
return m
models.py
from django.db import models
from .services import SEOService
class Dependency(models.Model):
dependency = models.SlugField(
'Зависимость', unique=True,
help_text='Перечислите зависимости через нижнее подчеркивание. Пример: brand_model'
)
def __str__(self) -> str:
return f'Зависимость {self.dependency}'
class Meta:
verbose_name = 'Зависимость'
verbose_name_plural = 'Зависимости'
class Seo(models.Model):
statuses = (
(1, 'Дефолтная'),
(2, 'Дополнительная')
)
_delimiter = SEOService().delimiter
dependencies = models.ManyToManyField(
Dependency,
verbose_name='Зависимости',
blank=True,
help_text='Оставьте пустым, если это дефолтный шаблон.'
)
h1 = models.CharField(
'Заголовок(h1)', max_length=200,
help_text=f'Если вы ввели Купить {_delimiter}, а зависимость - car,'
f' то после сохранения получится Купить car машину. '
f'Все {_delimiter} заменяются на соотв. им зависимости.'
)
title = models.CharField(
'Заголовок(title)', max_length=200,
help_text=f'Если вы ввели Купить {_delimiter}, а зависимость - car,'
f' то после сохранения получится Купить car машину. '
f'Все {_delimiter} заменяются на соотв. им зависимости.'
)
description = models.CharField(
'Описание', max_length=200,
help_text=f'Если вы ввели Купить {_delimiter}, а зависимость - car,'
f' то после сохранения получится Купить car машину. '
f'Все {_delimiter} заменяются на соотв. им зависимости.'
)
keywords = models.TextField(
'Ключевые слова',
help_text=f'Если вы ввели Купить {_delimiter}, а зависимость - car,'
f' то после сохранения получится Купить car машину. '
f'Все {_delimiter} заменяются на соотв. им зависимости.'
)
status = models.IntegerField('Статус', blank=True, choices=statuses, help_text='Не трогать руками', null=True)
def __str__(self) -> str:
return f'Настройка сео'
class Meta:
verbose_name = 'Настройка'
verbose_name_plural = 'Настройки'

Related

Django - annotate query for if queried user is followed by current user

So I have this code below that given a user_uuid I look up the info of a user. All request come with a auth token that contains the current user's uuid. If the current user queried for their own profile then the is_current_user key should be true, which is done with the item['is_current_user'] = user_uuid == request.user.uuid line and then I have another flag for if the current user is following the queried user which is item['is_following'] = FollowUser.objects.filter(follower=str(request.user.uuid), followee=user_uuid).exists(). As you can see these are very ugly hacks and I was wondering if I could add these values to the annotate function.
def get_user_info(request, user_uuid):
try:
query_annotated = User.objects.annotate(
follower_count=Count('followee_id', distinct=True),
followee_count=Count('follower_id', distinct=True)).get(pk=user_uuid)
except User.DoesNotExist as e:
return Response(dict(error=str(e),
user_message='User not found.'),
status=status.HTTP_404_NOT_FOUND)
serializer = FullUserSerializer(query_annotated)
# TODO: This is a hack the above annotation is not working
item = serializer.data
item['is_following'] = FollowUser.objects.filter(follower=str(request.user.uuid), followee=user_uuid).exists()
item['is_current_user'] = user_uuid == request.user.uuid
return Response(item, status=status.HTTP_200_OK)
Version 2
FullUserSerializer.py
class FullUserSerializer(serializers.ModelSerializer):
follower_count = serializers.IntegerField()
followee_count = serializers.IntegerField()
is_following = serializers.BooleanField()
is_current_user = serializers.BooleanField()
class Meta:
model = User
fields = ('uuid', 'username', 'avatar', 'created', 'follower_count',
'followee_count', 'is_current_user', 'is_following')
view.py
def get_user_info(request, user_uuid):
try:
query_annotated = User.objects.annotate(
follower_count=Count('followee_id', distinct=True),
followee_count=Count('follower_id', distinct=True),
is_following=Exists(
FollowUser.objects.filter(
follower=str(request.user.uuid),
followee=OuterRef("pk")
)
),
is_current_user=Case(
When(
pk=str(request.user.uuid),
then=Value(True)
),
default=Value(False),
output_field=BooleanField(),
),
).get(pk=user_uuid),
except User.DoesNotExist as e:
return Response(dict(error=str(e),
user_message='User not found.'),
status=status.HTTP_404_NOT_FOUND)
serializer = FullUserSerializer(query_annotated)
# TODO: This is a hack the above annotation is not working
# item = serializer.data
# item['is_following'] = FollowUser.objects.filter(follower=str(request.user.uuid), followee=user_uuid).exists()
# item['is_current_user'] = user_uuid == request.user.uuid
return Response(serializer.data, status=status.HTTP_200_OK)
model.py
class User(AbstractDatesModel):
uuid = models.UUIDField(primary_key=True)
username = models.CharField(max_length=USERNAME_MAX_LEN, unique=True, validators=[
MinLengthValidator(USERNAME_MIN_LEN)])
created = models.DateTimeField('Created at', auto_now_add=True)
updated_at = models.DateTimeField('Last updated at', auto_now=True, blank=True, null=True)
avatar = models.ImageField(upload_to=avatar_directory_path, blank=True, null=True)
#property
def avatar_url(self):
return self.avatar.url
Try this:
from django.db.models import Case, Exists, OuterRef, When, Value
query_annotated = User.objects.annotate(
# ...
is_following=Exists(
FollowUser.objects.filter(
follower=str(request.user.uuid),
followee=OuterRef("pk")
)
),
is_current_user=Case(
When(
pk=request.user.uuid,
then=Value(True)
),
default=Value(False),
output_field=BooleanField(),
),
).get(pk=user_uuid)

how to update using SIGNALS

models.py
class Rooms(models.Model):
objects = None
room_num = models.IntegerField(verbose_name='Комната')
room_bool = models.BooleanField(default=True,verbose_name='Релевантность')
category = models.CharField(max_length=150,verbose_name='Категория')
price = models.CharField(max_length=105,verbose_name='Цена (сум)')
def __str__(self):
return f'{self.room_num}'
class Meta:
verbose_name = 'Комнату'
verbose_name_plural = 'Комнаты'
class Registration(models.Model):
objects = None
rooms = models.ForeignKey(Rooms, on_delete=models.CASCADE,verbose_name='Номер',help_text='Номер в который хотите заселить гостя!')
first_name = models.CharField(max_length=150,verbose_name='Имя')
last_name = models.CharField(max_length=150,verbose_name='Фамилия')
admin = models.ForeignKey(User, on_delete=models.CASCADE,verbose_name='Администратор')
pasport_serial_num = models.CharField(max_length=100,verbose_name='Серия паспорта',help_text='*AB-0123456')
birth_date = models.DateField(verbose_name='Дата рождения')
img = models.FileField(verbose_name='Фото документа',help_text='Загружайте файл в формате .pdf')
visit_date = models.DateTimeField(
default=datetime.datetime(year=year, month=month, day=day, hour=datetime.datetime.now().hour,
minute=datetime.datetime.now().minute, second=00,),verbose_name='Дата прибытия')
leave_date = models.DateTimeField(
default=datetime.datetime(year=year, month=month, day=day + 1, hour=12, minute=00, second=00),verbose_name='Дата отбытия')
guest_count = models.IntegerField(default=1,verbose_name='Кол-во людей')
room_bool = models.BooleanField(default=False,verbose_name='Релевантность',help_text='При бронирование отключите галочку')
price = models.CharField(max_length=105,default='Появится после сохранения!',verbose_name='Цена (сум)')
def __str__(self):
return f'{self.rooms},{self.last_name},{self.first_name},{self.room_bool}'
class Meta:
verbose_name = 'Номер'
verbose_name_plural = 'Регистрация'
signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Registration
#receiver(post_save, sender=Registration)
def create_profile(sender, instance, created, **kwargs):
if created:
instance.rooms.room_bool = False
instance.rooms.save()
this is the code in the signals that updates the value from another model according to the value of another WHEN I CREATE (if in model 1 it will be False, then in model 2 it will also be False) how can I make it so that when EDITING changes the values
I recommend use pre_save signal for this situation
#receiver(pre_save, sender=Registration)
def create_profile(instance, *args, **kwargs):
if instance.pk #Its update
else: #Its create
django.db.models.signals.pre_save
This is sent at the beginning of a model’s save() method.
django.db.models.signals.post_save
Like pre_save but sent at the end of the save() method
your model will not has id when creating so you can catch with pk instance is data for your model
#receiver(post_save, sender=Registration)
def create_profile(sender, instance, created,**kwargs):
if created:
instance.rooms.room_bool = instance.room_bool
instance.rooms.save()
instance.price = instance.rooms.price
instance.save()
elif not created:
instance.rooms.room_bool = instance.room_bool
instance.rooms.save()

Django Simple History not logging user

I am using django-simple-history to log changes in my models. I have managed to save log of changes, but I always get None on field history_user.
I am following this tutorial.
This is my model:
class Reservation(models.Model):
UNCONFIRMED = 'UNCONFIRMED'
CONFIRMED = 'CONFIRMED'
CANCELED = 'CANCELED'
NO_SHOW = 'NO SHOW'
GO_SHOW = 'GO SHOW'
STATUS_CHOICES = (
(UNCONFIRMED, _('Unconfirmed')),
(CONFIRMED, _('Confirmed')),
(CANCELED, _('Canceled')),
(NO_SHOW, _('No show')),
(GO_SHOW, _('Go show')),
)
booking = models.CharField(max_length=25, verbose_name=_('Booking'))
agency = models.ForeignKey(Agency, on_delete=models.PROTECT, related_name='reservations', verbose_name=_('Agency'))
comment = models.TextField(null=True, blank=True, verbose_name=_('Comment'))
status = models.CharField(max_length=15, choices=STATUS_CHOICES, default=UNCONFIRMED, verbose_name=_('Status'))
confirmation_date = models.DateTimeField(null=True, blank=True, verbose_name=_("Confirmation Date"))
arrival_date = models.DateField(verbose_name=_('Arrival Date'))
departure_date = models.DateField(verbose_name=_('Departure Date'))
confirmation = models.CharField(max_length=15, null=True, blank=True, verbose_name=_('Confirmation Code'))
is_invoiced = models.BooleanField(default=False, verbose_name=_('Is invoiced?'))
euroamerica = models.BooleanField(default=False, verbose_name=_("Is Euroamerica sale"))
user = models.ForeignKey(User, null=True, blank=True, on_delete=models.PROTECT, related_name='reservations')
changed_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.PROTECT)
timestamp = models.DateTimeField(null=True, blank=True, auto_now_add=True)
handle_fee = models.FloatField(null=True, blank=True, verbose_name=_("Handle Fee"))
log = HistoricalRecords(related_name='history')
class Meta:
verbose_name = _('Reservation')
verbose_name_plural = _('Reservations')
permissions = (
('reservation_can_edit', 'Can Edit Reservation'),
('reservation_can_view', 'Can View Reservation'),
('reservation_can_view_history_log', 'Can View Reservation History Log')
)
def __str__(self):
return "[{}]{}".format(self.booking, self.agency)
def clean(self, *args, **kwargs):
if self.id is None:
try:
Reservation.objects.get(booking=self.booking)
except:
pass
else:
raise CustomValidation(_('Booking already exists.'), 'booking', status_code=status.HTTP_400_BAD_REQUEST)
if isinstance(self.arrival_date, str):
raise CustomValidation(_('Arrival date must be a valid date.'), 'arrival_date', status_code=status.HTTP_400_BAD_REQUEST)
if isinstance(self.departure_date, str):
raise CustomValidation(_('Departure date must be a valid date.'), 'departure_date', status_code=status.HTTP_400_BAD_REQUEST)
if self.arrival_date >= self.departure_date:
raise CustomValidation(_('Departure date must be later than Arrival date.'), 'departure_date', status_code=status.HTTP_400_BAD_REQUEST)
# elif self.arrival_date <= timezone.datetime.now().date():
# if self.id == None:
# raise CustomValidation(_('Arrival date must be later than today.'), 'arrival_date', status_code=status.HTTP_400_BAD_REQUEST)
if self.status == 'CONFIRMED' and self.confirmation is None:
raise CustomValidation(_('Must provide a confirmation number.'), 'confirmation', status_code=status.HTTP_400_BAD_REQUEST)
return True
def save(self, *args, **kwargs):
from GeneralApp.models import Parameter
self.booking = self.booking.replace(' ', '')
self.booking = self.booking.upper()
self.full_clean()
if self.confirmation is not None and self.status is not self.CONFIRMED:
if self.pk is not None:
try:
previous_reservation = Reservation.objects.get(id=self.pk)
except:
pass
else:
if previous_reservation.status == self.status and previous_reservation.confirmation != self.confirmation:
self.status = self.CONFIRMED
else:
self.status = self.CONFIRMED
if self.status == self.CONFIRMED and self.confirmation_date is None:
self.confirmation_date = timezone.now()
try:
self.handle_fee = Parameter.objects.get(name="Handle Fee").value
except:
self.handle_fee = None
super(Reservation, self).save(*args, **kwargs)
#property
def _history_user(self):
return self.changed_by
#_history_user.setter
def _history_user(self, value):
self.changed_by = value
I don't know what I am missing to get the user logged.
EDIT
Here is where I save Reservation:
def save_reservation(self, reservation_data, user, pk):
try:
reservation_to_save = models.Reservation.objects.get(booking=pk)
except:
raise CustomValidation(_("There is not such reservation {}".format(pk)), 'id', status.HTTP_400_BAD_REQUEST)
reservation_to_save.booking = reservation_data['booking']
reservation_to_save.agency = models.Agency.objects.get(id=reservation_data['agency'])
reservation_to_save.comment = reservation_data.get('comment', None)
reservation_to_save.status = reservation_data.get('status', 'UNCONFIRMED')
reservation_to_save.arrival_date = reservation_data['arrival_date']
reservation_to_save.departure_date = reservation_data['departure_date']
reservation_to_save.confirmation = reservation_data.get('confirmation', None)
reservation_to_save.is_invoiced = reservation_data.get('is_invoiced', False)
reservation_to_save.euroamerica = reservation_data.get('euroamerica', False)
reservation_to_save.save()
return reservation_to_save
According to the documentation you have to add HistoryRequestMiddleware to your middleware in settings.py
Like so:
MIDDLEWARE = [
# ...
'simple_history.middleware.HistoryRequestMiddleware',
]
This at least was the solution in my case.
If you don't want to use this middleware you can set the user manually, see this.

Getting FieldError in modelformset factory in django view.py

I am getting FieldError as :
Unknown field(s) (notedate) specified for AssistantNotes
When i call the page. It throws this error. I am using Django 1.9.5 and python 2.7.
I have notedate field in the AssistantNotes table in my db. If i delete "notedate" from modelformset_factory row in my view, it works. I couldnt solve why it is not showing notedate although it is in DB and in model. And generating error. The field is already in the model.
My view is :
def edit_assistant_notes(request):
isassistantsuperadmin = getUserPermissions(request) #Yes if 1, no if 0
list = getUserType(request)
userisassistant = list[2]
if userisassistant == "YES" or isassistantsuperadmin ==1:
list = getUserType(request)
type = list[0]
usertype = list[1] #"Nöbetçi Muavin":1 , "Yetkili":2
if request.method == 'GET':
if AssistantNotes.objects.filter(notedate=nicosia_date(datetime.today()).date()).count() == 0:
AssistantNotesFormsetFactory = modelformset_factory(AssistantNotes, fields=('time', 'notedate', 'categories', 'type', 'dailynote',))
else:
AssistantNotesFormsetFactory = modelformset_factory(AssistantNotes, fields=('time', 'notedate', 'categories', 'type', 'dailynote',), can_delete=True)
if usertype == 1:
formset = AssistantNotesFormsetFactory(queryset=AssistantNotes.objects.filter(notedate=nicosia_date(datetime.today()).date(), type=type))
elif usertype == 2:
formset = AssistantNotesFormsetFactory(queryset=AssistantNotes.objects.all().order_by("notedate", "time"))
helper = TableInlineHelper()
return render(request, 'edit-assistant-notes.html', {'formset': formset, 'helper': helper})
My model is :
class AssistantNotes(BaseModel):
categories = models.CharField(choices=CATEGORIES, default="GENERAL", max_length=100, verbose_name=_("CAT"))
time = models.CharField(choices=TIME, default="-------------", max_length=20, verbose_name=_("Time"))
dailynote = models.TextField(null=True, blank=True, verbose_name=_("Add Note"))
writer = models.TextField(null=True, blank=True, verbose_name=_("Adder"))
notedate = models.DateField(auto_now_add=True, db_index=True, verbose_name=_("Date"))
type = models.CharField(choices=SCHOOLTYPE, default="---", max_length=100, verbose_name=_("SchoolType"))
def __unicode__(self):
return "%s / %s" % (self.dailynote, self.categories)
class Meta:
ordering = ['dailynote']
How can i force this field to be editable ?
Instead of setting auto_now_add, you may override the save() method of the model, say
class AssistantNotes(BaseModel):
....
notedate = models.DateField(db_index=True, verbose_name=_("Date"))
def save(self, *args, **kwargs):
if not self.id:
self.notedate = timezone.now()
return super(AssistantNotes, self).save(*args, **kwargs)

Default value for ForeignKey field in django admin panel

I have two django Models: PageModel and RecordModel. They are registered in django admin panel. I want automatically create RecordModel object and assign it to (object of PageModel).record field if record is not selected in process of Page creation. (Django Admin - Add Page form) I tried to create form and used clean_record() method, but that not work. (debugger is not stopped there) How can i solve the problem?
Models (Sortable and SortableAdmin classes are part of adminsortable (https://github.com/iambrandontaylor/django-admin-sortable), but I think it does not really matter):
class Record(Sortable):
"""
Запись в книге почетных гостей
"""
class Meta(Sortable.Meta):
verbose_name = u'Запись'
verbose_name_plural = u'Записи'
author = models.CharField(verbose_name=u'Автор', max_length=255, unique=False, blank=False,
default=author_default)
def __unicode__(self):
return u'Запись {} ({})'.format(self.id, self.author)
class Page(Sortable):
"""
Страница книги почетных гостей
"""
class Meta(Sortable.Meta):
verbose_name = u'Страница'
verbose_name_plural = u'Страницы'
record = SortableForeignKey(Record, verbose_name=u'Запись', related_name='pages', blank=False, default=None)
image = models.ImageField(verbose_name=u'Картинка',
upload_to='pages',
default='',
help_text=u'Размер файла - до 10 MB. Формат PNG.',
validators=[ImageValidator(formats=['PNG'], max_size=10000000)])
updated = models.DateTimeField(verbose_name=u'Обновление', auto_now=True, null=True,
help_text=u'Время последнего изменения страницы на сервере')
def __unicode__(self):
return u'Страница {} ({})'.format(self.id, self.image)
Admin:
class PageInline(SortableTabularInline):
model = Page
#admin.register(Record)
class RecordAdmin(SortableAdmin):
list_display = ['author', 'pages_count']
inlines = [PageInline]
fields = ['author']
def pages_count(self, object):
return object.pages.count()
pages_count.short_description = u'Количество страниц'
pages_count.allow_tags = False
class PageAdminForm(forms.ModelForm):
def clean_record(self):
return self.cleaned_data["record"]
#admin.register(Page)
class PageAdmin(SortableAdmin):
list_display = ['__unicode__', 'image', 'author', 'updated']
form = PageAdminForm
readonly_fields = ['updated']
def get_fields(self, request, obj=None):
if obj:
return super(PageAdmin, self).get_fields(request, obj)
else:
return ['record', 'image']
def author(self, page):
return page.record.author
author.allow_tags = False
author.short_description = u'Автор записи'
I solved the problem as follows:
Change record field in Page model
record = SortableForeignKey(Record, verbose_name=u'Запись', related_name='pages', null=True, blank=True, default=None)
Add save_model() method to PageAdmin
def save_model(self, request, obj, form, change):
if obj.record is None:
record = Record.objects.create(author=BookConfiguration.get_solo().record_author_default + ' (' + timezone.localtime(timezone.now()).strftime('%Y-%m-%d %H:%M:%S') + ')')
record.save()

Categories