Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle Project URL Slug Changes #2889

Merged
merged 12 commits into from
Jul 20, 2023
Merged

Handle Project URL Slug Changes #2889

merged 12 commits into from
Jul 20, 2023

Conversation

ayanaar
Copy link
Contributor

@ayanaar ayanaar commented Jun 21, 2023

Fixes #2152

This pull request addresses the issue of properly redirecting old project slugs to the current ones after a project's slug has been updated. The previous approach using a decorator was refactored into a utility function for more efficient handling of outdated slugs. Below are the changes made:

  • ProjectSlugHistory Model: A new model called ProjectSlugHistory was created to keep a history of old slugs for each project. This model has fields for the project (a foreign key to the Project model), the old slug, and the timestamp of the change.

  • Pre-Save Signal: The signal create_slug_history was implemented to detect when a project's slug is updated. When a change is detected, a new ProjectSlugHistory instance is created, effectively saving the old slug.

  • get_project_or_redirect Utility Function: This function attempts to get a project with the given slug. If the project doesn't exist, it checks if the slug is in the ProjectSlugHistory and if so, it redirects to the current project slug URL. If the old slug is not found in the history, it raises an Http404 error.

  • Utility Function Usage: Several views (e.g., localization, translate, project) have been updated to use the new utility function.

  • Test Cases: Comprehensive test cases have been added to validate the functionality of the get_project_or_redirect utility function. This includes scenarios with old slugs, missing slugs, and nonexistent slugs for the project, translate, and localization views. These tests ensure the correctness of the redirection process.

This new approach ensures a smooth user experience when slugs are changed, as old links will continue to function correctly by redirecting to the new location. The newly added tests and updated docstrings also enhance code maintainability and clarity.

@ayanaar ayanaar changed the title Add Slug History and Redirect for Project Model Handle Project URL Slug Changes Jun 21, 2023
@ayanaar ayanaar marked this pull request as ready for review June 21, 2023 21:24
@ayanaar ayanaar requested a review from mathjazz June 21, 2023 21:24
Copy link
Collaborator

@mathjazz mathjazz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!

There's two minor changes I requested, as well as a more holistic approach to redirecting several URLs.

@@ -292,6 +292,10 @@ class TranslationAdmin(admin.ModelAdmin):
raw_id_fields = ("entity",)


class ProjectSlugHistoryAdmin(admin.ModelAdmin):
raw_id_fields = ("project",)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line is not needed, it would be nice however to show the created_at field in the form.

created_at = models.DateTimeField(auto_now_add=True)


@receiver(pre_save, sender=Project)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please move the signal to the signals.py file?

# Redirect to the current project slug
return redirect("pontoon.projects.project", slug=slug_history.project.slug)
else:
raise Http404("Project not found.")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are quite a few views in which we'd need a check like this. For example, we'd also want to redirect the localization dashboard (/{locale}/{project}), translate view (/{locale}/{project}/all-resources/), as well as several AJAX views spread across multiple urls.py files.

Can we instead introduce a few URL patters that will catch old project slugs as proposed initially?

@ayanaar ayanaar marked this pull request as draft June 26, 2023 14:54
@ayanaar ayanaar marked this pull request as ready for review June 27, 2023 16:12
@ayanaar ayanaar marked this pull request as draft July 6, 2023 23:02
…ug redirection logic and enhanced docstrings
@ayanaar ayanaar marked this pull request as ready for review July 7, 2023 19:52
@ayanaar ayanaar requested a review from eemeli July 7, 2023 19:52
Copy link
Member

@eemeli eemeli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm a bit concerned with the proposed order of operations, for two reasons:

  1. This adds a ProjectSlugHistory lookup to every request, even when the slug is the current one used by a project. This seems rather wasteful.
  2. What happens if a slug is re-used? I can think of two potentially problematic scenarios:
    1. If a project's slug is renamed aabbaa, there's potential redirect loop that may be entered.
    2. If one project's slug is renamed aabb, and then a different project starts using the aa slug, requests for it get redirected to the bb project.

Both of the above concerns would be resolved if the ProjectSlugHistory lookup were only done after an initial project slug lookup failed. Is there a reason this would not work?

Test cases should also be included for the scenarios mentioned above.

Comment on lines 649 to 653
# Try to find a project using the current slug
try:
Project.objects.get(slug=slug)
# If found, continue with the original view function
return view_func(request, *args, **kwargs)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is better, but still adds a project lookup to each request that's then discarded. Is there a way to do as I suggested earlier, and to include this redirection in the error handling rather than proactively?

In each of the three functions that use this decorator, there's a call to get_object_or_404() for something like Project.objects.get(slug=slug). Could that call be changed to trigger this flow, instead of the current decorator approach?

Copy link
Member

@eemeli eemeli left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good! Some questions and nitpicks on line comments.

@mathjazz Do you want to look at this again, as its shape has evolved somewhat, or should we just merge & push to staging?

pontoon/base/utils.py Outdated Show resolved Hide resolved
pontoon/base/utils.py Outdated Show resolved Hide resolved
pontoon/base/utils.py Outdated Show resolved Hide resolved
@mathjazz
Copy link
Collaborator

mathjazz commented Jul 17, 2023

This looks good, nice work!

I'm pushing the branch to stage now.

The only thing I'm left wondering about is whether we should redirect just the three main URLs (current state), or all.

@mathjazz mathjazz requested review from mathjazz and eemeli July 18, 2023 14:32
@mathjazz
Copy link
Collaborator

@flodolo Could you take this for a spin on stage and provide some feedback here?

Essentially you should go to Admin, change slug for a project (or projects) and then test if the app behaves as expected.

@flodolo
Copy link
Collaborator

flodolo commented Jul 19, 2023

This seems to work correctly, although there's an edge case which is not considered, and to me it feels like a bug.

I changed the slug for AMO from amo to amo1, then to amo2. There's a check preventing me to use a slug already in use, which is good (BTW, the message Project with this Slug already exists. feels a bit weird with an article at the beginning, i.e. A project with this Slug already exists.). Links with both amo  and amo1 correctly redirect to amo2.

Then I went to a different project (AMO Linter), and switched its slug to amo1. Surprisingly, it let me do that. I assumed we should prevent reusing an existing slug, even if it's not actively used by a project?

@mathjazz
Copy link
Collaborator

@ayanaar Let's extend slug validation to also prevent the use of historic slugs.

@eemeli
Copy link
Member

eemeli commented Jul 19, 2023

Then I went to a different project (AMO Linter), and switched its slug to amo1. Surprisingly, it let me do that. I assumed we should prevent reusing an existing slug, even if it's not actively used by a project?

This should be fine, and already handled by this PR. Historical slugs are not required to be unique, but on access the most recent slug wins.

For an example of a different system that works this way, see GitHub repo renames: They'll redirect old URLs until and unless you define a new repo with the same name as a past one had.

In my opinion, no further work is required on this PR before finally merging it.

@mathjazz
Copy link
Collaborator

On Pontoon Call we've decided to move on with the current state of the patch. Nice work, @ayanaar!

@mathjazz mathjazz merged commit d913a04 into mozilla:master Jul 20, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Redirect old project slugs to the new one
4 participants