How to realize the hard delete function in Python Django? - python

I have a User realization class, in there I want to have two type delete function:
class User(AbstractUser):
nickname = models.CharField(max_length=16, blank=True, null=True) # nickname
real_name = models.CharField(max_length=12, null=True, blank=True)
phone = models.CharField(max_length=18) # telephone
...
status = models.CharField(max_length=1, null=True, default=1)
def soft_del(self):
self.status = 4
return True
def hard_delete_user(self):
# what should I do there?
return True
you see, one is soft delete a user, the other is hard delete a user.
I mean the soft delete, a user still in the database, but I will not query it by my code, the hard delete is delete it from the database table.
How to realize the hard_delete_user function?

This is just self.delete() [Django-doc], given I understood you correctly:
class User(AbstractUser):
# ...
def hard_delete_user(self):
self.delete()
return True
You might want to restrict this function, for example only delete the User object, given it was already softly deleted, like:
class User(AbstractUser):
# ...
def hard_delete_user(self):
# example: restrict deletion to softly deleted
if self.status == 4:
self.delete()
return True
return False

Related

DRF: based on the validation of field x, stop the validation of field y and delete from data

I have a form where the user can post a deal, and for that they can choose to either upload an image (x), or provide a link to an already uploaded one on the internet (y). So when either of the fields exists and is valid, I would like to dismiss the other field from validation. Here is a simplified Model:
class Deal(models.Model):
user = models.ForeignKey(settings.AUTH_USER_MODEL, related_name='deals',
on_delete=models.CASCADE)
title = models.CharField(max_length=1024, blank=False, null=False)
slug = models.SlugField(max_length=1024, unique=True, blank=True)
poster = models.ImageField()
poster_url = models.CharField(max_length=1024, blank=True, null=True)
created = models.DateField(auto_now_add=True)
updated = models.DateField(auto_now=True)
And here's my serializer:
class DealSerializer(serializers.ModelSerializer):
user = UserSerializer(many=False, read_only=True)
class Meta:
model = Deal
fields = '__all__'
def validate_poster_url(self, value):
poster = self.initial_data.get("poster")
if value == "" and poster == "":
# set to mutable
self.initial_data._mutable = True
self.initial_data.delete("poster")
raise serializers.ValidationError(
"Deal must either have a Poster or a Poster URL")
return value
def validate_poster(self, value):
poster_url = self.initial_data.get("poster_url")
if value != "":
if poster_url == "":
# set to mutable
self.initial_data._mutable = True
self.initial_data.delete("poster_url")
raise serializers.ValidationError(
"Deal must either have a Poster or a Poster URL")
return value
def create(self, validated_data):
deal, _ = Deal.objects.update_or_create(**validated_data)
return deal
When I submit for example a poster_url, the poster field still gets validated and I get a validation error:
poster: ["The submitted data was not a file. Check the encoding type on the form."]
I have tried to "delete" the required field from the initial_data as an attempt, but that wouldn't work, and I also tried to use the global validate(), but it was never called. I would really appreciate any help. Thank you.
Well, Deal.poster is not optional, so that's why Django complains. I don't think you can handle this on the serializer level. Take a look at Django - how to make ImageField/FileField optional?.

Iterate Over objects in an m2m_changed signal

I have the following setup:
order/models.py
class Order(models.Model):
order_deliverer = models.ManyToManyField(Deliverer, related_name='deliverer')
deliveringstatus = models.BooleanField(default=False)
order/signals.py
#receiver(m2m_changed, sender=Order.deliverer.through)
def m2m_changed_order_deliverer(sender, instance, **kwargs):
if kwargs['action'] == 'post_add':
instance.deliveringstatus = True
instance.save()
#changes the order model's, on-delivery status to TRUE once an order_deliverer has been appended onto its ManyToManyField.
account/models.py
class Deliverer(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE, primary_key=True, related_name='deliverer')
busy = models.BooleanField(default=False)
#receiver(m2m_changed, sender=Order.order_deliverer.through)
def m2m_changed_deliverer_profile(sender, instance, **kwargs):
if kwargs['action'] == 'post_add':
#print(instance.order_deliverer.through.objects.all())
obj = instance.order_deliverer.through.objects.last().deliverer
obj.busy = True
obj.save()
#alters the deliverer's profile status to busy.
1. My problem is on handling iterations. If the order's order_deliverer field has multiple user objects, the account signal is only handled on the last object.
2. How do I iterate & alter each deliverer's profile.
I'm thinking of looping via 'for' but I first need to be able to fetch individual user objects rather than first or last.

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

How to get objects from db under multiple constraints?

I'm trying to figure out how to get particular set of objects from database in Django efficiently.
I can do this using nested loops but I think that it's not a best idea.
I have models Language and UserProfile which has a property method verified_languages which returns a set of Language objects for this UserProfile.
What I want exactly is to create a static method of Language model called get_to_languages(language) which returns a set of all languages of all UserProfiles if these UserProfiles has language attribute in verified_languages property method.
So if there were there these Users -
1st. user:
name = 'Peter'
userprofile.verified_languages = ['english','german','arabic','french']
2nd. user:
name = 'Evgen'
userprofile.verified_languages = ['german','arabic','spanish']
3rd. user:
name = 'Anton'
userprofile.verified_languages = ['russian','arabic','italian']
And I call method get_to_languages(Languages.objects.get(name='german'))
It would return a set of Evgen's and Peter's languages because they know german.
Is it possible to do this using filter?
My old solution:
#staticmethod
def get_languages_to(language):
userprofiles = UserProfile.objects.all()
result = set()
for up in userprofiles:
if language in up.languages_verified:
result.update(up.languages_verified)
result.remove(language)
return list(result)
USERPROFILE:
class UserProfile(models.Model):
user = models.OneToOneField(User, related_name='userprofile', help_text=_('Related user'))
date_of_birth = models.DateField(null=True, blank=True, help_text=_('Date of birth'))
telephone = models.CharField(max_length=40, null=True, blank=True, help_text=_('Your telephone number'))
IBAN = models.CharField(max_length=40, null=True, blank=True, help_text=_('Bank account unique number'))
created = models.DateTimeField(auto_now_add=True)
modified = models.DateTimeField(auto_now=True)
MARITAL_STATUS_CHOICES = (
('single', 'Single'),
('married', 'Married'),
('separated', 'Separated'),
('divorced', 'Divorced'),
('widowed', 'Widowed'),
)
marital_status = models.CharField(max_length=40, choices=MARITAL_STATUS_CHOICES, null=True, blank=True)
HOW_DO_YOU_KNOW_ABOUT_US_CHOICES = (
('coincidence', u'It was coincidence'),
('relative_or_friends', 'From my relatives or friends'),
)
how_do_you_know_about_us = models.CharField(max_length=40, choices=HOW_DO_YOU_KNOW_ABOUT_US_CHOICES, null=True,
blank=True)
is_translator = models.BooleanField(default=False)
language_levels = models.ManyToManyField('LanguageLevel', blank=True, related_name='translators')
rating = models.IntegerField(default=0)
number_of_ratings = models.BigIntegerField(default=0)
#property
def languages(self):
"""
Returns: all languages of current user include not-verified ones
"""
return [x.language for x in self.language_levels.all()]
#property
def languages_verified(self):
"""
Returns: verified languages of current user
"""
return [x.language for x in self.language_levels.exclude(level__name='unknown')]
def passive_skill(self, language):
"""
True if user has at least passive skill (lowest level) of the language
Args:
language: Language object
Returns: Bool
"""
if language in self.languages_verified:
return True
return False
Well, I think it could be done this way: first, get all LanguageLevel that contains current language and their level_name is not unknown, then get all userprofiles from the result LanguageLevel. In the end, get all languages linked to these userprofiles(apparently it's untested, but please try it out):
language_levels = LanguageLevel.objects.filter(language=language) \
.exclude(level__name='unknown')
userprofiles = UserProfile.objects.filter(language_levels__in=language_levels)
all_languages = profiles.values_list('language_levels__language',
flat=True).distinct()
Note that the __in is very inefficient but that's if you don't have huge amount of records it should be OK. Just pay attention to the performance and see if that goes well.

Django-powered library checkout system

I am working on a library system to manage certain items in our office, I don't need a full-blown integrated library system so I decided to hand roll one with Django.
Below is a simplified version of my model:
class ItemObjects(models.Model):
# Static Variables
IN_STATUS = 'Available'
OUT_STATUS = 'Checked out'
MISSING = 'Missing'
STATUS_CHOICES = (
(IN_STATUS, 'Available'),
(OUT_STATUS, 'Checked out'),
(MISSING, 'Missing'),
)
# Fields
slug = models.SlugField(unique=True)
date_added = models.DateField(auto_now_add=True)
last_checkin = models.DateTimeField(editable=False, null=True)
last_checkout = models.DateTimeField(editable=False, null=True)
last_activity = models.DateTimeField(editable=False, null=True)
status = models.CharField(choices=STATUS_CHOICES, default=IN_STATUS, max_length=25)
who_has = models.OneToOneField(User, blank=True, null=True)
times_out = models.PositiveIntegerField(default=0, editable=False)
notes = models.CharField(blank=True, max_length=500)
history = models.TextField(blank=True, editable=False)
pending_checkin = models.BooleanField(default=False)
pending_transfer = models.BooleanField(default=False)
At first I was using a method on ItemObject to process checking out an item to a user and who_has was an EmailField because I couldn't get a CharfField to populate with the logged in user's name, but I figured using a OneToOneField is probably closer to the "right" way to do this.. While who_has was an EmailField, the following method worked:
def check_out_itemobject(self, user):
user_profile = user.get_profile()
if self.status == 'Available' and self.who_has == '':
self.status = 'Checked out'
self.who_has = user.email
self.last_checkout = datetime.datetime.now()
self.last_activity = datetime.datetime.now()
self.times_out += 1
if self.history == '':
self.history += "%s" % user_profile.full_name
else:
self.history += ", %s" % user_profile.full_name
if user_profile.history == '':
user_profile.history += self.title
else:
user_profile.history += ", %s" % self.title
else:
return False # Not sure is this is "right"
user_profile.save()
super(ItemObjects, self).save()
Now that I am using a OneToOneField this doesn't work, so I started looking at using a subclass of ModelForm but none of the cases I saw here on SO seemed to apply for what I am trying to do; my form would be a button, and that's it. Here are some of the questions I looked at:
Django: saving multiple modelforms simultaneously (complex case)
(Django) (Foreign Key Issues) model.person_id May not be NULL
django update modelform
So was I on the right track with a sort of altered save() method, or would a ModelForm subclass be the way to go?
EDIT/UPDATE: Many thanks to #ChrisPratt!
So I am trying to get Chris Pratt's suggestion for showing ItemHistory to work, but when I try to render it on a page I get an AttributeError that states "'User' object has no attribute 'timestamp'". So my question is, why is it complaining about a User object when last_activity is an attribute on the ItemObject object ?
My view:
#login_required
def item_detail(request, slug):
item = get_object_or_404(Item, slug=slug)
i_history = item.last_activity
user = request.user
return render_to_response('items/item_detail.html',
{ 'item' : item,
'i_history': i_history,
'user' : user })
I do not see why a User object is coming up at this point.
EDIT2: Nevermind, history is clearly a M2M field whose target is User. That's why!
Assuming users will log in and check out books to themselves, then what you most likely want is a ForeignKey to User. A book will only have one User at any given time, but presumably Users could check out other items as well. If there is some limit, even if the limit is actually one per user, it would be better to validate this in the model's clean method. Something like:
def clean(self):
if self.who_has and self.who_has.itemobject_set.count() >= LIMIT:
raise ValidationError('You have already checked out your maximum amount of items.')
Now, you checkout method has a number of issues. First, status should be a defined set of choices, not just random strings.
class ItemObject(models.Model):
AVAILABLE = 1
CHECKED_OUT = 2
STATUS_CHOICES = (
(AVAILABLE, 'Available'),
(CHECKED_OUT, 'Checked Out'),
)
...
status = models.PositiveIntegerField(choices=STATUS_CHOICES, default=AVAILABLE)
Then, you can run your checks like:
if self.status == self.STATUS_AVAILABLE:
self.status = self.STATUS_CHECKED_OUT
You could use strings and a CharField instead if you like, as well. The key is to decouple the static text from your code, which allows much greater flexibility in your app going forward.
Next, history needs to be a ManyToManyField. Right now, your "history" is only who last checked the item out or what the last item the user checked out was, and as a result is pretty useless.
class ItemObject(models.Model):
...
history = models.ManyToManyField(User, through='ItemHistory', related_name='item_history', blank=True)
class ItemHistory(models.Model):
CHECKED_OUT = 1
RETURNED = 2
ACTIVITY_CHOICES = (
(CHECKED_OUT, 'Checked Out'),
(RETURNED, 'Returned'),
)
item = models.ForeignKey(ItemObject)
user = models.ForeignKey(User)
activity = models.PostiveIntegerField(choices=ACTIVITY_CHOICES)
timestamp = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ['-timestamp'] # latest first
Which then allows you to get full histories:
some_item.history.all()
some_user.item_history.all()
To add a new history, you would do:
ItemHistory.objects.create(item=some_item, user=some_user, activity=ItemHistory.CHECKED_OUT)
The auto_now_add attribute ensures that the timestamp is automatically set when the relationship is created.
You could then actually get rid of the last_checkout and last_activity fields entirely and use something like the following:
class ItemObject(models.Model):
...
def _last_checkout(self):
try:
return self.history.filter(activity=ItemHistory.CHECKED_OUT)[0].timestamp
except IndexError:
return None
last_checkout = property(_last_checkout)
def _last_activity(self):
try:
return self.history.all()[0].timestamp
except IndexError:
return None
last_activity = property(_last_activity)
And, you can then use them as normal:
some_item.last_checkout
Finally, your checkout method is not an override of save so it's not appropriate to call super(ItemObject, self).save(). Just use self.save() instead.

Categories