Wagtail 4 FieldPanel issue with ForeignKey - django

I have the following setup in wagtail 4.0.4:
#register_snippet
class Copyright(models.Model):
class CopyrightType(models.TextChoices):
PH1 = "PH1", _("Phase 1")
PH2 = "PH2", _("Phase 2")
type = models.CharField(
max_length=3,
choices=CopyrightType.choices,
unique=True,
)
class ReportPage(Page):
copyright = models.ForeignKey(
Copyright,
to_field="type",
default="PH2",
on_delete=models.SET_DEFAULT,
)
Now the set up works just fine. But if I add the FieldPanel:
class ReportPage(Page):
copyright = models.ForeignKey(
Copyright,
to_field="type",
default="PH2",
on_delete=models.SET_DEFAULT,
)
promote_panels = Page.promote_panels + [
FieldPanel("copyright"),
]
I get the following error:
ValueError: Field 'id' expected a number but got 'PH2'.
Is there any way to tell the FieldPanel to look at the field referenced in the "to_field" option instead of the primary key?

Related

Filtering X most recent entries in each category of queryset

Question is regarding filtering X most recent entries in each category of queryset.
Goal is like this:
I have a incoming queryset based on the following model.
class UserStatusChoices(models.TextChoices):
CREATOR = 'CREATOR'
SLAVE = 'SLAVE'
MASTER = 'MASTER'
FRIEND = 'FRIEND'
ADMIN = 'ADMIN'
LEGACY = 'LEGACY'
class OperationTypeChoices(models.TextChoices):
CREATE = 'CREATE'
UPDATE = 'UPDATE'
DELETE = 'DELETE'
class EntriesChangeLog(models.Model):
content_type = models.ForeignKey(
ContentType,
on_delete=models.CASCADE,
)
object_id = models.PositiveIntegerField(
)
content_object = GenericForeignKey(
'content_type',
'object_id',
)
user = models.ForeignKey(
get_user_model(),
verbose_name='user',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='access_logs',
)
access_time = models.DateTimeField(
verbose_name='access_time',
auto_now_add=True,
)
as_who = models.CharField(
verbose_name='Status of the accessed user.',
choices=UserStatusChoices.choices,
max_length=7,
)
operation_type = models.CharField(
verbose_name='Type of the access operation.',
choices=OperationTypeChoices.choices,
max_length=6,
)
And I need to filter this incoming queryset in a such way to keep only 4 most recent objects (defined by access_time field) in each category. Categories are defined by ‘content_type_id’ field and there are 3 possible options.
Lets call it ‘option1’, ‘option2’ and ‘option3’
This incoming queryset might contain different amount of objects of 1,2 or all 3 categories. This is can’t be predicted beforehand.
DISTINCT is not possible to use as after filtering operation this queryset might be ordered.
I managed to get 1 most recent object in a following way:
# get one most recent operation in each category
last_operation_time = Subquery(
EntriesChangeLog.objects.filter(user=OuterRef('user')).values('content_type_id').
annotate(last_access_time=Max(‘access_time’)).values_list('last_access_time', flat=True)
)
queryset.filter(access_time__in=last_operation_time)
But I have a hard time to figure out how to get last 4 most recent objects instead of last one.
This is needed for Django-Filter and need to be done in one query.
DB-Postgres 12
Do you have any ideas how to do such filtration?
Thanks...
pk_to_rank = queryset.annotate(rank=Window(
expression=DenseRank(),
partition_by=('content_type_id',),
order_by=F('access_time').desc(),
)).values_list('pk', 'rank', named=True)
pks_list = sorted(log.pk for log in pk_to_rank if log.rank <= value)
return queryset.filter(pk__in=pks_list)
Managed to do it only this way by spliting queryset in 2 parts. Option with 3 unions is also possible but what if we have 800 options instead 3 - make 800 unions()??? ges not...

get the actual value of the human-readable name from choices in django models

I made this model:
name = models.CharField(max_length=3, choices=(
('sat', "Saturday"),
('sun', "Sunday"),
('mon', "Monday"),
('tue', "Tuesday"),
('wed', "Wednesday"),
('thu', "Thursday"),
('fri', "Friday"),
), null=False, blank=False, primary_key=True)
and when I want to get objects I can only access the human-readable name of choices.
how can I get an object by human-readable name?
I tried this but got an error:
Days.objects.get(name='saturday')
in your case: instance.get_name_display()
Django docs: get_FOO_display()
It ain't pretty, but you could reverse lookup via the models meta attribute:
class Days(models.Model)
name = models.CharField(max_length=3, choices=(
('sat', "Saturday"),
('sun', "Sunday"),
('mon', "Monday"),
('tue', "Tuesday"),
('wed', "Wednesday"),
('thu', "Thursday"),
('fri', "Friday"),
), null=False, blank=False, primary_key=True)
#classmethod
def human_name_to_choice(cls, human_readable_str):
return {b: a for a, b in cls._meta.get_field('name').choices}.get(human_readable_str, 'NUL')
# ...
Days.objects.get(name=Days.human_name_to_choice('Saturday'))
You can modify it to be case insensitive (so that it would work in your example)
But be careful:
Human readable strings must not be unique!
If the human readable string can not be found in the choices, it returns a dummy ('NUL') - but this could be added at a later time and lead to confusion

Django/Python: How to iterate through a list in a dictionary for migration/migrate data

I’m trying to set up migration files to load values into my tables. I am creating countries/states in my tables. I would like to set it up in a way that I can put each country in its own file, and then run through all the countries on migration. I successfully got all the names in separately, but I’m trying to make it easier.
UPDATE:
Thanks for help, I got it all to work the way I want it to. Here is my end result.
models:
class Country(models.Model):
country_code = models.CharField(
primary_key=True,
max_length=3,
verbose_name='Country Code',
help_text='3 Letter Country Code',
)
country_name = models.CharField(
"Country Name",
max_length=30,
unique=True,
)
class State(models.Model):
key = models.AutoField(
primary_key=True,
)
country = models.ForeignKey(
'Country',
on_delete=models.CASCADE,
verbose_name='Country',
help_text='State located in:'
)
state_name = models.CharField(
"State or Province",
max_length=100,
)
state_code = models.CharField(
"State Code",
max_length=30,
help_text='Mailing abbreviation'
)
Migration Data files:
"""
Canada migration information.
Stored in dictionary = canada
copy into migration_data_migrate_countries
from .migration_country_Canada import canada
Models:
Country
State
"""
# Country
import_country = ['CAN', 'CANADA',]
# State
import_states = [
['AB', 'ALBERTA'],
['BC', 'BRITISH COLUMBIA'],
['MB', 'MANITOBA'],
etc...
]
# 'import' into migration file
canada = {
'country': import_country,
'states': import_states,
}
Second country file:
# Country
import_country = ['USA', 'UNITED STATES',]
# State
import_states = [
['AL', 'ALABAMA'],
['AK', 'ALASKA'],
['AZ', 'ARIZONA'],
etc...
]
# 'import' into migration file
united_states = {
'country': import_country,
'states': import_states,
}
import method:
# Keep imports alphabetized by country name.
from .migration_country_Canada import canada
from .migration_country_UnitedStates import united_states
list_of_countries = [
canada,
united_states,
]
def migrate_countries(apps, schema_editor):
Country = apps.get_model('app', 'Country')
State = apps.get_model('app', 'State')
for country in list_of_countries:
import_country = country['country']
states = country['states']
current_country = Country.objects.get_or_create(
country_code=import_country[0],
country_name=import_country[1]
)
# False = already exists. True = created object.
print(import_country, current_country)
for s in states:
state_country = Country.objects.get(
country_code=import_country[0])
state = State.objects.get_or_create(
country=state_country,
state_code=s[0],
state_name=s[1],
)
# False = already exists. True = created object.
print(s, state)
Then I run python3 manage.py makemigrations --empty app and edit the migration file:
from django.db import migrations
from app.migration_countries.migration_data_migrate_countries import *
class Migration(migrations.Migration):
dependencies = [
('app', '0001_initial'),
]
operations = [
migrations.RunPython(migrate_countries),
]
Then run python3 manage.py migrate and get results in the terminal
... # Added Canada first which is why YUKON is False
['YT', 'YUKON'] (<State: State object (75)>, False)
['USA', 'UNITED STATES'] (<Country: Country object (USA)>, True)
['AL', 'ALABAMA'] (<State: State object (76)>, True)
...
And when I want to add another country, france, I make a france file and change my list to:
list_of_countries = [
canada,
france,
united_states,
]
And any changes I make should stay up to date. Hopefully. Any suggestions, feel free to let me know.
I guess the problem the way you initially are trying to approach this task. I think you should update your dictionary:
canada = {
'country': import_country,
'states': import_states,
}
Keep in mind that a key should be an immutable object.
for country in list_of_countries:
import_country = country['country']
states = country['states']
current_country = Country.objects.get_or_create(
country_code=c[0],
country_name=c[1]
)
current_country.states = states
I am not sure what you are trying to achieve, but if you provide a better description I can update my answer.

How can I implement the template using the Django model below?

I just started programming using Django and making a dental chart interface but having troubles making the template. I guess my challenge started with how I designed my previous models. A dental chart is composed of 32 teeth, each tooth has its own attributes. Below is my model:
class DentalChart(models.Model):
patient = models.ForeignKey(PatientInfo, on_delete=models.CASCADE)
date = models.DateField()
dentist = models.CharField(max_length=100)
PERIODONTAL_CHOICES = (
('Gingivitis', 'Gingivitis'),
('Early Periodontitis', 'Early Periodontitis'),
('Moderate Periodontitis', 'Moderate Periodontitis'),
('Advanced Periodontitis', 'Advanced Periodontitis'),
('N/A', 'N/A')
)
periodontal_screening = MultiSelectField(choices=PERIODONTAL_CHOICES, default='')
OCCLUSION_CHOICES = (
('Class(Molar)', 'Class(Molar)'),
('Overjet', 'Overjet'),
('Overbite', 'Overbite'),
('Midline Deviation', 'Midline Deviation'),
('Crossbite', 'Crossbite'),
('N/A', 'N/A')
)
occlusion = MultiSelectField(choices=OCCLUSION_CHOICES, default='')
APPLIANCES_CHOICES = (
('Orthodontic', 'Orthodontic'),
('Stayplate', 'Stayplate'),
('Others', 'Others'),
('N/A', 'N/A')
)
appliances = MultiSelectField(choices=APPLIANCES_CHOICES, default='')
TMD_CHOICES = (
('Clenching', 'Clenching'),
('Clicking', 'Clicking'),
('Trismus', 'Trismus'),
('Muscle Spasm', 'Muscle Spasm'),
('N/A', 'N/A')
)
TMD = MultiSelectField(choices=TMD_CHOICES, default='')
class Tooth(models.Model):
chart = models.ForeignKey(DentalChart, on_delete=models.CASCADE)
tooth_id = models.CharField(max_length=2)
CONDITIONS_LIST = (
('', ''),
('Present Teeth', 'P'),
('Decayed', 'D'),
('Missing due to Carries', 'M'),
('Missing (Other Causes)', 'Mo'),
('Impacted Tooth', 'Im'),
('Supernumerary Tooth', 'Sp'),
('Root Fragment', 'Rf'),
('Unerupted', 'Un')
)
condition = models.CharField(max_length=30, choices=CONDITIONS_LIST, default='')
RESTORATIONS_LIST = (
('', ''),
('Amalgam Filling', 'A'),
('Composite Filling', 'Co'),
('Jacket Crown', 'Jc'),
('Abutment', 'Ab'),
('Pontic', 'Po'),
('Inlay', 'In'),
('Implant', 'Imp'),
('Sealants', 'S'),
('Removable Denture', 'Rm')
)
restoration = models.CharField(max_length=30, choices=RESTORATIONS_LIST, default='')
I created this UI using standard HTML form so that I can visualize how it would look like. But I haven't figured out how to incorporate Django so that it would be easier to refer to the tables once I create functions such as adding and editing dental charts.
Check image here
I am having a hard time to show the dental chart on my template. I hope to get some answers and tips from the experts. Thank you!

how to get the latest in django model

In this model:
class Rank(models.Model):
User = models.ForeignKey(User)
Rank = models.ForeignKey(RankStructure)
date_promoted = models.DateField()
def __str__(self):
return self.Rank.Name.order_by('promotion__date_promoted').latest()
I'm getting the error:
Exception Value:
'str' object has no attribute 'order_by'
I want the latest Rank as default. How do I set this?
Thanks.
Update #1
Added Rank Structure
class RankStructure(models.Model):
RankID = models.CharField(max_length=4)
SName = models.CharField(max_length=5)
Name = models.CharField(max_length=125)
LongName = models.CharField(max_length=512)
GENRE_CHOICES = (
('TOS', 'The Original Series'),
('TMP', 'The Motion Picture'),
('TNG', 'The Next Generation'),
('DS9', 'Deep Space Nine'),
('VOY', 'VOYAGER'),
('FUT', 'FUTURE'),
('KTM', 'KELVIN TIMELINE')
)
Genre = models.CharField(max_length=3, choices=GENRE_CHOICES)
SPECIALTY_OPTIONS = (
('CMD', 'Command'),
('OPS', 'Operations'),
('SCI', 'Science'),
('MED', 'Medical'),
('ENG', 'Engineering'),
('MAR', 'Marine'),
('FLT', 'Flight Officer'),
)
Specialty = models.CharField(max_length=25, choices=SPECIALTY_OPTIONS)
image = models.FileField(upload_to=image_upload_handler, blank=True)
This is the Rank_structure referenced by Rank in Class Rank.
THe User Foreign key goes to the standard User table.
The reason that you’re getting an error is because self.Rank.Name is not a ModelManager on which you can call order_by. You’ll need an objects in there somewhere if you want to call order_by. We can’t help you with the django formatting for the query you want unless you also post the model definitions as requested by several commenters. That said, I suspect that what you want is something like:
def __str__(self):
return self.objects.filter(Rank_id=self.Rank_id).order_by('date_promoted').latest().User.Name