Skip to content

Commit

Permalink
Add duplicate_font management command.
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiocaccamo authored Jan 25, 2024
2 parents 28f9e15 + a2b8ccf commit 6d4eb3c
Show file tree
Hide file tree
Showing 25 changed files with 236 additions and 116 deletions.
5 changes: 0 additions & 5 deletions .flake8

This file was deleted.

14 changes: 5 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,22 +14,18 @@ repos:
- id: django-upgrade
args: ["--target-version", "4.1"]

- repo: https://github.com/psf/black
rev: 23.1.0
hooks:
- id: black

- repo: https://github.com/pycqa/isort
rev: 5.12.0
hooks:
- id: isort
args: ["--profile", "black"]

- repo: https://github.com/PyCQA/flake8
rev: 6.0.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.1.9
hooks:
- id: flake8
exclude: "robocjk/tests"
- id: ruff
args: [--fix, --exit-non-zero-on-fix]
- id: ruff-format

- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.4.0
Expand Down
11 changes: 11 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,14 @@ exclude = '''
| venv
)/
'''

[tool.isort]
profile = "black"

[tool.ruff]
ignore = ["C901", "E501", "E731"]
line-length = 88
select = ["B", "B9", "C", "E", "F", "W"]

[tool.ruff.mccabe]
max-complexity = 10
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
attrs==23.1.0
chardet==5.2.0
Django==4.2.7
Django==5.0.1
django-admin-interface==0.28.3
django-admin-rangefilter==0.10.0
django-ckeditor==6.7.0
Expand Down
2 changes: 1 addition & 1 deletion robocjk/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ def _export_as_csv(modeladmin, request, queryset):
csv = unicodecsv.writer(response, encoding="utf-8")

if header:
row = [n for n in field_names_display]
row = field_names_display
csv.writerow(row)

for obj in queryset:
Expand Down
4 changes: 1 addition & 3 deletions robocjk/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -587,9 +587,7 @@ def status_display(self, obj):
border-radius: 3px;
line-height: 1.0;
white-space: nowrap;
""".format(
obj.status_color
)
""".format(obj.status_color)
html = f'<span style="{css}">{obj.get_status_display()}</span>'
return mark_safe(html)

Expand Down
4 changes: 2 additions & 2 deletions robocjk/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def __init__(self, host, username, password):
no need to do anything.
"""
if not host or not any(
[host.startswith(protocol) for protocol in ["http://", "https://"]]
host.startswith(protocol) for protocol in ["http://", "https://"]
):
raise ValueError(f"Invalid host: {host}")
if not username:
Expand Down Expand Up @@ -78,7 +78,7 @@ def _connect(self):
"Unable to call RoboCJK APIs at host: {} - Exception: {}".format(
self._host, e
)
)
) from e

# obtain the auth token to prevent 401 error on first call
self.auth_token()
Expand Down
31 changes: 14 additions & 17 deletions robocjk/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -260,12 +260,11 @@ def serialize_deep_component(obj, options=None):
if return_layers:
data["layers"] = []
if return_made_of:
data["made_of"] = list(
[
serialize_atomic_element(glif_obj, options)
for glif_obj in obj.atomic_elements.all()
]
)
data["made_of"] = [
serialize_atomic_element(glif_obj, options)
for glif_obj in obj.atomic_elements.all()
]

if return_used_by:
data["used_by"] = list(obj.character_glyphs.values(*CHARACTER_GLYPH_ID_FIELDS))
return data
Expand Down Expand Up @@ -297,18 +296,16 @@ def serialize_character_glyph(obj, options=None):
if obj.id not in made_of_character_glyphs_refs:
made_of_character_glyphs_refs.add(obj.id)
options["made_of_character_glyphs_refs"] = made_of_character_glyphs_refs
made_of_character_glyphs = list(
[
serialize_character_glyph(glif_obj, options)
for glif_obj in obj.character_glyphs.all()
]
)
made_of_deep_components = list(
[
serialize_deep_component(glif_obj, options)
for glif_obj in obj.deep_components.all()
made_of_character_glyphs = [
serialize_character_glyph(glif_obj, options)
for glif_obj in obj.character_glyphs.all()
]
)

made_of_deep_components = [
serialize_deep_component(glif_obj, options)
for glif_obj in obj.deep_components.all()
]

data["made_of"] = made_of_character_glyphs + made_of_deep_components
if return_used_by:
used_by_character_glyphs = list(
Expand Down
60 changes: 24 additions & 36 deletions robocjk/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -396,24 +396,18 @@ def glif_lock(request, params, user, font, *args, **kwargs):
glif_obj.lock_by(user, save=True)

data = {
"atomic_elements": list(
[
atomic_element_obj.serialize(options=params)
for atomic_element_obj in atomic_elements_list
]
),
"deep_components": list(
[
deep_components_obj.serialize(options=params)
for deep_components_obj in deep_components_list
]
),
"character_glyphs": list(
[
character_glyphs_obj.serialize(options=params)
for character_glyphs_obj in character_glyphs_list
]
),
"atomic_elements": [
atomic_element_obj.serialize(options=params)
for atomic_element_obj in atomic_elements_list
],
"deep_components": [
deep_components_obj.serialize(options=params)
for deep_components_obj in deep_components_list
],
"character_glyphs": [
character_glyphs_obj.serialize(options=params)
for character_glyphs_obj in character_glyphs_list
],
}
return ApiResponseSuccess(data)

Expand Down Expand Up @@ -461,24 +455,18 @@ def glif_unlock(request, params, user, font, *args, **kwargs):
glif_obj.unlock_by(user, save=True)

data = {
"atomic_elements": list(
[
atomic_element_obj.serialize(options=params)
for atomic_element_obj in atomic_elements_list
]
),
"deep_components": list(
[
deep_components_obj.serialize(options=params)
for deep_components_obj in deep_components_list
]
),
"character_glyphs": list(
[
character_glyphs_obj.serialize(options=params)
for character_glyphs_obj in character_glyphs_list
]
),
"atomic_elements": [
atomic_element_obj.serialize(options=params)
for atomic_element_obj in atomic_elements_list
],
"deep_components": [
deep_components_obj.serialize(options=params)
for deep_components_obj in deep_components_list
],
"character_glyphs": [
character_glyphs_obj.serialize(options=params)
for character_glyphs_obj in character_glyphs_list
],
}
return ApiResponseSuccess(data)

Expand Down
4 changes: 2 additions & 2 deletions robocjk/management/commands/cleanup_rcjk.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ def handle(self, *args, **options):
try:
project_obj = Project.objects.get(uid=project_uid)
project_obj.cleanup_file_system()
except Project.DoesNotExist:
except Project.DoesNotExist as project_error:
message = f"Invalid project_uid, project with uid {project_uid!r} doesn't exist."
self.stderr.write(message)
raise CommandError(message)
raise CommandError(message) from project_error
elif all_projects:
# export all projects
projects_qs = Project.objects.prefetch_related("fonts")
Expand Down
4 changes: 2 additions & 2 deletions robocjk/management/commands/delete_rcjk.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ def handle(self, *args, **options):
font_uid = options.get("font_uid")
try:
font_obj = Font.objects.select_related("project").get(uid=font_uid)
except Font.DoesNotExist:
except Font.DoesNotExist as font_error:
raise CommandError(
f'Invalid font_uid, font with uid "{font_uid}" doesn\'t exist.'
)
) from font_error

font_obj.available = False
font_obj.save()
Expand Down
140 changes: 140 additions & 0 deletions robocjk/management/commands/duplicate_font.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
from django.core.management.base import BaseCommand, CommandError

from robocjk.models import (
AtomicElement,
AtomicElementLayer,
CharacterGlyph,
CharacterGlyphLayer,
DeepComponent,
Font,
)


class Command(BaseCommand):
help = "Duplicate .rcjk font"

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def add_arguments(self, parser):
# https://docs.python.org/3/library/argparse.html
parser.add_argument(
"--source-font-uid",
required=True,
help="The uid 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' of the font to duplicate (source).",
)
parser.add_argument(
"--target-font-uid",
required=True,
help="The uid 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx' of the font duplicated (target).",
)
parser.add_argument(
"--target-font-clear",
action="store_true",
help="Delete existing Atomic Elements, Atomic Elements Layers, Deep Components, "
"Character Glyphs, Character Glyphs Layers before duplicating source font.",
)

def handle(self, *args, **options): # noqa: C901
source_font_uid = options.get("source_font_uid")
try:
source_font_obj = Font.objects.get(uid=source_font_uid)
except Font.DoesNotExist as font_error:
message = f"Invalid source_font_uid, font with uid '{source_font_uid}' doesn't exist."
self.stderr.write(message)
raise CommandError(message) from font_error

target_font_uid = options.get("target_font_uid")
try:
target_font_obj = Font.objects.get(uid=target_font_uid)
except Font.DoesNotExist as font_error:
message = f"Invalid target_font_uid, font with uid '{target_font_uid}' doesn't exist."
self.stderr.write(message)
raise CommandError(message) from font_error

target_font_clear = options.get("target_font_clear")
if target_font_clear:
message = "Unsupported option target_font_clear (not implemented yet, TODO)"
raise CommandError(message)

source_project_obj = source_font_obj.project
if source_project_obj.export_running:
message = f"Unable to duplicate font, there is an export running for '{source_project_obj.name}'."
self.stderr.write(message)
raise CommandError(message)

target_project_obj = target_font_obj.project
if target_project_obj.export_running:
message = f"Unable to duplicate font, there is an export running for '{target_project_obj.name}'."
self.stderr.write(message)
raise CommandError(message)

self.stdout.write(
f"Duplicating font '{source_project_obj.name} / {source_font_obj.name}' "
f"-> '{target_project_obj.name} / {target_font_obj.name}'"
)

# duplicate font
font_obj = source_font_obj
font_clone_obj = target_font_obj
self.stdout.write(f"Duplicating font '{font_obj.name}' ...")

self.stdout.write("Duplicating atomic elements and atomic elements layers ...")
for ae_obj in font_obj.atomic_elements.all():
ae_clone_obj, _ = AtomicElement.objects.get_or_create(
font=font_clone_obj,
name=ae_obj.name,
defaults={
"data": ae_obj.data,
},
)
for ae_layer_obj in ae_obj.layers.all():
ae_layer_clone_obj, _ = AtomicElementLayer.objects.get_or_create(
glif=ae_clone_obj,
group_name=ae_layer_obj.group_name,
defaults={
"data": ae_layer_obj.data,
},
)

self.stdout.write("Duplicating deep components ...")
for dc_obj in font_obj.deep_components.all():
dc_clone_obj, _ = DeepComponent.objects.get_or_create(
font=font_clone_obj,
name=dc_obj.name,
defaults={
"data": dc_obj.data,
},
)

self.stdout.write(
"Duplicating character glyphs and character glyphs layers ..."
)
for cg_obj in font_obj.character_glyphs.all():
cg_clone_obj, _ = CharacterGlyph.objects.get_or_create(
font=font_clone_obj,
name=cg_obj.name,
defaults={
"data": cg_obj.data,
},
)
for cg_layer_obj in cg_obj.layers.all():
cg_layer_clone_obj, _ = CharacterGlyphLayer.objects.get_or_create(
glif=cg_clone_obj,
group_name=cg_layer_obj.group_name,
defaults={
"data": cg_layer_obj.data,
},
)

self.stdout.write("Updating atomic elements many to many relations ...")
for ae_obj in font_clone_obj.atomic_elements.all():
ae_obj.update_components()

self.stdout.write("Updating deep components many to many relations ...")
for dc_obj in font_clone_obj.deep_components.all():
dc_obj.update_components()

self.stdout.write("Updating character glyphs many to many relations ...")
for cg_obj in font_clone_obj.character_glyphs.all():
cg_obj.update_components()
Loading

0 comments on commit 6d4eb3c

Please sign in to comment.