Skip to content

Commit

Permalink
urls: Generic object path parsing
Browse files Browse the repository at this point in the history
This allows to introduce component categorization in the future, see #263
  • Loading branch information
nijel committed Aug 7, 2023
1 parent 2a941fe commit 9bede1d
Show file tree
Hide file tree
Showing 10 changed files with 85 additions and 49 deletions.
5 changes: 4 additions & 1 deletion weblate/trans/mixins.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def get_reverse_url_kwargs(self):
"""Return kwargs for URL reversing."""
raise NotImplementedError

def get_url_path(self):
raise NotImplementedError

def reverse_url(self, name=None):
"""Generic reverser for URL."""
if name is None:
Expand All @@ -34,7 +37,7 @@ def get_absolute_url(self):
return self.reverse_url()

def get_commit_url(self):
return self.reverse_url("commit")
return reverse("commit", kwargs={"path": self.get_url_path()})

def get_update_url(self):
return self.reverse_url("update")
Expand Down
3 changes: 3 additions & 0 deletions weblate/trans/models/component.py
Original file line number Diff line number Diff line change
Expand Up @@ -1158,6 +1158,9 @@ def get_reverse_url_kwargs(self):
"""Return kwargs for URL reversing."""
return {"project": self.project.slug, "component": self.slug}

def get_url_path(self):
return [self.project.slug, self.slug]

def get_widgets_url(self):
"""Return absolute URL for widgets."""
return get_site_url(
Expand Down
3 changes: 3 additions & 0 deletions weblate/trans/models/project.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@ def get_reverse_url_kwargs(self):
"""Return kwargs for URL reversing."""
return {"project": self.slug}

def get_url_path(self):
return [self.slug]

def get_widgets_url(self):
"""Return absolute URL for widgets."""
return get_site_url(reverse("widgets", kwargs={"project": self.slug}))
Expand Down
3 changes: 3 additions & 0 deletions weblate/trans/models/translation.py
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ def get_reverse_url_kwargs(self):
"lang": self.language.code,
}

def get_url_path(self):
return [self.component.project.slug, self.component.slug, self.language.code]

def get_widgets_url(self):
"""Return absolute URL for widgets."""
return get_site_url(
Expand Down
3 changes: 3 additions & 0 deletions weblate/trans/tests/test_git_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ def setUp(self):
self.user.save()

def get_test_url(self, prefix):
if prefix == "commit":
return getattr(getattr(self, self.TEST_TYPE), f"get_{prefix}_url")()

return reverse(
f"{prefix}_{self.TEST_TYPE}",
kwargs=getattr(self, f"kw_{self.TEST_TYPE}"),
Expand Down
3 changes: 2 additions & 1 deletion weblate/trans/tests/test_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ def setUp(self):
# Create project to have some test base
self.component = self.create_component()
self.project = self.component.project
self.translation = self.get_translation()
# Invalidate caches
self.project.stats.invalidate()
cache.clear()
Expand All @@ -104,7 +105,7 @@ def setUp(self):
self.kw_lang_project = {"project": self.project.slug, "lang": "cs"}

# Store URL for testing
self.translation_url = self.get_translation().get_absolute_url()
self.translation_url = self.translation.get_absolute_url()
self.project_url = self.project.get_absolute_url()
self.component_url = self.component.get_absolute_url()

Expand Down
45 changes: 13 additions & 32 deletions weblate/trans/views/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from weblate.utils import messages
from weblate.utils.errors import report_error
from weblate.utils.lock import WeblateLockTimeoutError
from weblate.utils.views import get_component, get_project, get_translation
from weblate.utils.views import get_component, get_project, get_translation, parse_path


def execute_locked(request, obj, message, call, *args, **kwargs):
Expand All @@ -37,21 +37,6 @@ def execute_locked(request, obj, message, call, *args, **kwargs):
return redirect_param(obj, "#repository")


def perform_commit(request, obj):
"""Helper function to do the repository commit."""
if not request.user.has_perm("vcs.commit", obj):
raise PermissionDenied

return execute_locked(
request,
obj,
gettext("All pending translations were committed."),
obj.commit_pending,
"commit",
request.user,
)


def perform_update(request, obj):
"""Helper function to do the repository update."""
if not request.user.has_perm("vcs.update", obj):
Expand Down Expand Up @@ -135,23 +120,19 @@ def perform_file_scan(request, obj):

@login_required
@require_POST
def commit_project(request, project):
obj = get_project(request, project)
return perform_commit(request, obj)


@login_required
@require_POST
def commit_component(request, project, component):
obj = get_component(request, project, component)
return perform_commit(request, obj)

def commit(request, path):
obj = parse_path(request, path)
if not request.user.has_perm("vcs.commit", obj):
raise PermissionDenied

@login_required
@require_POST
def commit_translation(request, project, component, lang):
obj = get_translation(request, project, component, lang)
return perform_commit(request, obj)
return execute_locked(
request,
obj,
gettext("All pending translations were committed."),
obj.commit_pending,
"commit",
request.user,
)


@login_required
Expand Down
16 changes: 3 additions & 13 deletions weblate/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -421,19 +421,9 @@
),
# VCS manipulation - commit
path(
"commit/<name:project>/",
weblate.trans.views.git.commit_project,
name="commit_project",
),
path(
"commit/<name:project>/<name:component>/",
weblate.trans.views.git.commit_component,
name="commit_component",
),
path(
"commit/<name:project>/<name:component>/<name:lang>/",
weblate.trans.views.git.commit_translation,
name="commit_translation",
"commit/<object_path:path>/",
weblate.trans.views.git.commit,
name="commit",
),
# VCS manipulation - update
path(
Expand Down
15 changes: 13 additions & 2 deletions weblate/utils/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later

from django.urls import register_converter
from django.urls.converters import StringConverter
from django.urls.converters import PathConverter, StringConverter


class WeblateSlugConverter(StringConverter):
Expand All @@ -22,13 +22,24 @@ class WidgetExtensionConverter(StringConverter):
regex = "(png|svg)"


class OptionalPathConverter(StringConverter):
class OptionalPathConverter(PathConverter):
regex = "(info/|git-upload-pack)[a-z0-9_/-]*|"


class ObjectPathConverter(PathConverter):
regex = "[^/]+(/[^/]+){0,2}"

def to_python(self, value):
return value.split("/")

def to_url(self, value):
return "/".join(value)


def register_weblate_converters():
register_converter(WeblateSlugConverter, "name")
register_converter(GitPathConverter, "gitpath")
register_converter(WordConverter, "word")
register_converter(WidgetExtensionConverter, "extension")
register_converter(OptionalPathConverter, "optionalpath")
register_converter(ObjectPathConverter, "object_path")
38 changes: 38 additions & 0 deletions weblate/utils/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@
from django.views.generic.edit import FormView

from weblate.formats.models import EXPORTERS, FILE_FORMATS
from weblate.lang.models import Language
from weblate.trans.models import Component, Project, Translation
from weblate.utils import messages
from weblate.utils.errors import report_error
from weblate.utils.stats import ProjectLanguage
from weblate.vcs.git import LocalRepository

SORT_KEYS = {
Expand Down Expand Up @@ -147,6 +149,42 @@ def get_sort_name(request, obj=None):
}


def parse_path(request, path: list[str], skip_acl=False):
# First level is always project
project = get_project(request, path.pop(0), skip_acl=skip_acl)
if not path:
return project

# Project/language special case
if path[0] == "-" and len(path) == 2:
language = get_object_or_404(Language, code=path[1])
return ProjectLanguage(project, language)

# Component/category structure
parent = project
component = None
while path:
slug = path.pop(0)
try:
component = parent.component_set.get(slug=slug)
except Component.DoesNotExist as error:
raise Http404(f"Object {slug} not found in {parent}") from error
else:
component.acting_user = request.user
if not skip_acl:
request.user.check_access_component(component)
break

# Nothing left, return current object
if not path:
return component

if len(path) > 1:
raise Http404(f"Invalid path left: {'/'.join(path)}")

return get_object_or_404(component.translation_set, language__code=path[0])


def get_translation(request, project, component, lang, skip_acl=False):
"""Return translation matching parameters."""
translation = get_object_or_404(
Expand Down

0 comments on commit 9bede1d

Please sign in to comment.