Django: keep log after deleting a foreignkey - django

My question is simple:
I have a Folder model, and I have a Log model. Here are the simplified examples:
class Folder(models.Model):
name = models.CharField(max_length=100)
class Log(models.Model):
folder = models.ForeignKey(Folder, on_delete="I NEED HELP HERE")
What this means is, A folder is created by a User, and each action done on the folder, is logged by using the Log model (user-edited folder, the user did this with folder, etc...)
What I want to do is simple: if some admin deletes the folder completely, I want to still keep the name of the folder. All actions in the on_delete attribute, do not let me do that.
Any ideas?
PS: those are not the real models, just an illustration of what I want to do. The real ones are 100+ lines of code :P

You can implement your own on_delete trigger, since the on_delete just expects a callable that will be called if the object to which it refers is deleted: Django does triggering management itself. But that will likely only make it more complicated.
I think it might be better to make Folder a "soft-deleted" model. Here in case you delete a model, it is not deleted from the database, but the database has a column with a boolean that specifies if the record is "deleted". If it is deleted, it will normally not be included in standard querysets on the model, but there are still ways to obtain the item to which it refers. Furthermore you can later recover a deleted object by "undeleting" it. You can for example make use of the django-softdelete package [GitHub].

I think I found an answer, all I did was to set a default value, then put on_delete=models.SET_DEFAULT.
The default value is a variable returned from a function, declared right above the model, taking the username and storing it in a different table.

Related

How would I add a dropdown box that contains all the different type of models in my program?

I want a drop down box of all the models in my program.
I made a myModels model that has a charfield of 100. I tried to add the choice tuple and then reference it in the model.
helper_choices = []
for my_model in django.apps.apps.get_models():
helper_choices.append((my_model._meta.verbose_name, my_model._meta.verbose_name))
MODEL_CHOICES = tuple(helper_choices)
model_name = models.CharField(max_length=100, choices=MODEL_CHOICES, default='')
However since this is occurring during the loading of the models phase, I get an error "Models aren't loaded yet." What would be a workaround to this problem?
It is indeed impossible to call django.apps.apps.get_models() from the top-level of a models module, since the model's registry is currently being populated. This would create an infine recursion.
Also, choices defined at the model level are freezed in migrations, so everytime your models list changes you'd need a migration.
And finally, you may want to handle the case of "legacy" models - if you remove a model from your project, records pointing to it won't validate anymore.
I also note that you start your question with
I want a drop down box of all the models in my program.
So it looks it's more for UI purpose ?
Anyway: the simple solution is to leave the choices argument out of your models and only specify it in the form where you need this models selection. You may also want to use the canonical app_label.model_name as effective value and only use the verbose name for display (unless you don't care about having unusable data, that is).

Django viewable or clickable filepath field

I'm a little new to the inter-workings of Django and I would like to display a simple dynamic folder path field that opens to the given path when clicked so a user can view all the files in that path. I'm trying to do this in django admin site change form but am unclear and confused of how to do so. Below is my model.
class Order(models.Model):
order_number = models.IntegerField(verbose_name='LS #', unique=True)
order_name = models.ForeignKey(recs.RecipeControl, related_name='recipe')
# Something like this is I think what I want.
folder_path = models.FilePathField(path=get_path)
def get_path(self):
return str(self.order_number)+"_"+self.order_name
I'm puzzled as how to properly go about this because I can't seem to reference "self" to do this, especially if the record doesn't already exist. I've looked at a few other Q&A's but none of them dealt with the admin site and after a bit of reading I'm convinced that I may have to override one of the save methods but don't understand which one and where to place my method. Thanks in advance
EDIT
After reading through the comments recommended below I think what I want is different than what I had originally thought. The folder path still needs to be dynamic.
However, what I'm looking to do is check on new and existing records whether a folder exists in a given directory (MEDIA_ROOT?) based on model data, then create that directory or update it's name if it changes and save the folders path in the FilePathField. I'm pretty confident that this can be done by overriding that save_model method of the ModelAdmin, no?

How to replace a foreignkey with another using a migration

I'd like to replace an existing ForeignKey pointing at my User model with one pointing at a profile model.
The change in the model is:
created_by=models.ForeignKey(settings.AUTH_USER_MODULE)
To:
created_by=models.ForeignKey(settings.PROFILE_MODEL)
The auto-generated migration looks like (with constants subbed in):
migrations.AlterField(
model_name=MODEL,
name='created_by',
field=models.ForeignKey(to=settings.PROFILE_MODEL),
preserve_default=True,
),
I also have ManyToManyFields to deal with as well. What I have in my head is I'd like a function to run on each MODEL object to resolve the user object to the profile object. How would I go about doing this?
The relationship between user and profile is (and vice versa):
User.profile = Profile
Edit: Forgot to mention, if the auto-generated migration is run you get the following error:
ValueError: Lookup failed for model referenced by field
APP1.MODEL.created_by: APP2.PROFILE_MODEL
As I understand now you want to migrate only our app without expecting anything to be changed to the global auth User model. Then it's easy. Migrations work nice with symbolic settings names.
I tried it with Django 1.7. It is possible to switch between settings.AUTH_USER_MODEL and settings.PROFILE_MODEL back and forth without any problem. A migration can be created and applied after every change. The tested model had also a ManyToManyField and mutual relationships between User and Profile.
I see you have APP1 and APP2. Maybe you make migrations for both and they are circular dependent so that a part of other application migration should be applied before the current one migration can be completely applied and vice versa. It can be simplified by spliting a change to more smaller and making automatic migrations after every change so that they are less dependent. A OneToOneField is better than two mutual foreign keys and its reverse relation is even so useful. A foreign key can be changed temporarily to IntegerField(null=True) in the worst case in order to simplify data migration. It is really viable more or less nice.
The question looked nice initially, but the problem should be better specified to be reproducible.
Edited by removing the original text after reading information in comments:

Smarter removing objects with many-to-many relationship in Django admin interface

I'd like to remove some object with many-to-many relationship using Django admin interface. Standard removing also removes all related objects and the list of removed objects displayed on confirmation page. But I don't need to remove related objects!
Assume we have ContentTopic and ContentItem:
class ContentTopic(models.Model):
name = models.CharField()
code = models.CharField()
class ContentItem(models.Model):
topic = models.ManyToManyField(ContentTopic, db_index=True,\
blank=True, related_name='content_item')
So, I'd like to remove ContentTopic instance using Django admin, but I don't need remove all related ContentItems. So, confirmation page should display only ContentTopic instance to remove.
What is the best way to handle this?
This happens so, coz its developed to do so.
If you want to change this behaviour, the one way can be over-riding delete method of django.db.models.Model.
This delete() method actually does two things, first gathering a list of all dependent objects and delete them. So here, you can override it, to get that list of dependent objects, iterating over it and set their reference to None, instead of deleting them. And thus deleting the concerned object cleanly.
May be if you want this behavior throughout, you can extend a class from django.db.models.Models, override delete(), and extend all your models from this new class.

Django - logical delete

I want to make the following modification to the Django framework.
I want it to create a "deleted" field for each model I create.
I want it to be checked as deleted when I delete it from the admin page instead of being physically deleted.
I do not want these records checked as deleted to be listed.
I'm new to Django, I'm seeing if I can do what I want to do with it easily. I need this change because it's the way we currently work.
So far these are the changes I have made, I would like to understand how the whole Django framewok works inside but I'm so far from that, is there any documentation online which explains clearly how the inside framework parts/files/modules/classes work together, the specific role of each one, etc
In the base.py file, in the modelbase class, below this code,
for obj_name, obj in attrs.items():
new_class.add_to_class(obj_name, obj)
I added,
from django.db import models
new_class.add_to_class('deleted', models.BooleanField())
When it creates a model it adds the "deleted" field to it.
In the base.py file, in the save method, I changed what it was there for
self.deleted = True
self.save()
So, now it check as deleted a record instead of physically delete it.
Now what I want is those records not to be listed.
I don't understand why you're modifying the framework code instead of putting your deleted field in a model base class that all of your models extend from.
Nevertheless, a nice way to filter those records out would be to add a custom manager to the model (or your base model class, if you choose to create one). To this manager, override the get_query_set method as described here. In your overridden method, add a exclude(deleted=True) filter.
Take a look at the Django-logicaldelete app, You just inherit your models from their provided Model class and you get Logical delete for all of them.
It comes with an adminModel as well so you can manage logically deleted models there too.
Override the delete() method in your model class, set the deleted attribute there
Create a custom manager which will filter by deleted attribute and set it as the default one (objects = MyDeletedManager)