Skip to content

Standard way of bringing users back to starting point after completing side tasks#4347

Merged
ajrbyers merged 26 commits into
masterfrom
3899-next-urls
Feb 26, 2025
Merged

Standard way of bringing users back to starting point after completing side tasks#4347
ajrbyers merged 26 commits into
masterfrom
3899-next-urls

Conversation

@joemull

@joemull joemull commented Aug 7, 2024

Copy link
Copy Markdown
Member

Closes #3899.

Fixes #4291.
Fixes #4042.
Fixes #4321.

Users often have a main task (e.g. submit an article) and a side task (e.g. log in / register) that they need to complete during the main task.

This PR introduces a standard pattern for keeping track of main tasks through a sequence of side task views and templates.

It includes:

  • half a dozen new template tags and logic functions

  • numerous updates to views and templates, especially to avoid hard-coding unpredictable URL elements

  • new documentation both where it was missing and for new code

  • ~40 new tests (see note below on middleware)

  • fixing four bugs encountered in doing this work

  • migrations for the removal of two deprecated fields that held outdated help text

  • refactoring the user_login_orcid function (see note below)

  • migrations for updated setting values related to password resets and account confirmation

Note on one change to the middleware

Testing the templates across themes is very difficult when using the Django test Client, because of the way we load theme directories. Initially I wrote a helper that would allow me to patch template_override_middleware.Loader.get_theme_dirs. It looked like this:

def get_theme_dirs(journal):
    """
    Together with the mock library,
    this lets you test public-facing views, once for each theme.

    Example usage:

    @patch('utils.template_override_middleware.Loader.get_theme_dirs')
    def test_a_view_for_each_theme(self, get_theme_dirs):
        for theme_dirs in testing_helpers.get_theme_dirs():
            get_theme_dirs.response_value = theme_dirs
            response = self.client.get('/my/path/', {})
            self.assertIn(
                'The text I am looking for',
                response.content.decode(),
            )
    """
    request = Request()
    request.journal = journal
    with request_context(request):
        engine = Engine()
        loader = template_override_middleware.Loader(engine)
        dirs = loader.engine.dirs
        return [
            [os.path.join(settings.BASE_DIR, 'themes', 'clean', 'templates')] + dirs,
            [os.path.join(settings.BASE_DIR, 'themes', 'OLH', 'templates')] + dirs,
            [os.path.join(settings.BASE_DIR, 'themes', 'material', 'templates')] + dirs,
        ]

However this did not work, because the Django test client handles the creation of the request independently. It gives you no way to modify the request when using Client.get. So the request used in my helper was different than the one used in the test, and the theme dirs were missing a lot of template locations that are important for includes in the templates I am testing. Basically, the Django test client’s request is artificially stale in comparison to Janeway’s typical requests.

To make the test run in an environment that more closely resembles production, I decided to make use of the simple query parameter theme=clean and theme=OLH, which we use for debugging. I expanded the if clause to include settings.IN_TEST_RUNNER.

If there is another way of mocking the themes comprehensively which can be used effectively with the Django test Client, I’m happy to consider it.

Note on integration with ORCiD

There is some tricky juggling of view logic and context in between this branch and the ORCiD work that Esther has been putting through.

Esther and I successfully merged some of our work in Esther’s PR about prefilling the registration form with info from ORCiD.

But there is another PR that has been open since Sept 2024 that this work will interfere with. @ajrbyers please assign me as a reviewer if you would like me to work with Esther to resolve the differences.

In terms of the ORCiD work on this branch, the user_login_orcid view was getting really complicated and hard to read, so I refactored the view logic and documented the behavior with comments. Hopefully it is now clear how next and action work independently but are both needed in the state parameter that is sent to ORCiD authentication and received back.

Future work

If we like this implementation, we can eventually refactor existing return URLs using the same pattern, so that we do not have to reason about code like this:

<td>{% if can_edit_file_flag %}
<a href="{% url 'article_file_replace' 'id' article.pk file.pk %}?return={{ request.path|urlencode }}%3Freturn%3D{{ request.GET.return|urlencode }}"><i
class="fa fa-cloud-upload">&nbsp;</i></a>{% endif %}</td>
<td>{% if can_edit_file_flag %}
<a href="{% url 'file_delete' article.pk file.pk %}?return={{ request.path|urlencode }}%3Freturn%3D{{ request.GET.return|urlencode }}"><i
class="fa fa-trash">

Comment thread src/core/logic.py Outdated
@joemull joemull changed the title Carry next URL thorugh authentication Carry next URL through authentication Aug 7, 2024
@joemull joemull marked this pull request as ready for review February 6, 2025 18:14
@joemull joemull requested a review from mauromsl February 6, 2025 18:14
@joemull

joemull commented Feb 6, 2025

Copy link
Copy Markdown
Member Author

@mauromsl this is now ready for another review. Note the updated description with lots of explanation of choices.

@joemull joemull changed the title Carry next URL through authentication Standard way of bringing users back to starting point after completing side tasks Feb 7, 2025

@mauromsl mauromsl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks Joe, this looks really good. Reviewing the ORCID changes was a bit more challenging than I'd like, however the tests did help the process a lot. makes me realise that we need integration level tests here as we did with DOAJ

I've added what I think are mostly minor comments in line.

As you mentioned in the description there is a fair bit of refactoring now needed on #4399, although I do think it is already conflicting with other changes that made it to master.

A more general question I have has to do with the login links and their use of next URL. It seems like we are going to always redirect the user to the page they were on when they hit login. I think this makes sense in most contexts, but for the landing/homepage, wouldn't it make sense to send the user to the dashboard? The way I would implement this is by not usin the url_with_next in the homepage and let the Janeway backend view determine the default routing to the dashboard

Comment thread src/core/logic.py Outdated
The value of 'next' on the request, or the value of 'next_url',
should be in decoded form.

:param request: HttpRequest, optionally containing 'next' in GET

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Seems that what we want is the next GET parameter, calling this function should not require a request object. While I can see scenarios where it is somewhat useful, I at least make it optional, but having a single parameter makes this function easier to reason about scenarios such as passing both arguments and makes for a more solid signature.

It would also better align its signature with regular urlresolvers.reverse which does not require the entire request context either.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah that occurred to me earlier but I didn't get around to it. Will do it now.

Comment thread src/core/templatetags/next_url.py Outdated
Comment on lines +35 to +32
else:
# This is the only way to refer to press URLs
next_url = request.build_absolute_uri()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'm not entirely sure why this is needed, Request.build_absolute_uri will alias to request.get_full_path when no location argument is used

https://github.com/django/django/blob/e7a9d20380ab7ef42cc10e0c9b08e382ed75ab0d/django/http/request.py#L216

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yeah I know the build_absolute_uri method uses the same function for the path, but it also includes the domain, and I don't know of another way to refer to press URLs accurately.

I've made a demo of the problem I'm facing: 39eebbc

Check this branch out and run the test on press to see the failing test. If you then uncomment my code including the build_absolute_uri call for press requests, the test passes. Any alternative suggestions welcome.

This issue is also documented in #4577 , where I name it as a path issue, but it might be an issue for domain mode as well.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The problem exhibited in 39eebbc is related to how the test is setup. It uses an incomplete request object mockup rather than a real request, so there are no headers from where to borrow a URL path. I've changed it to use Django's test client and the test is passing now.
Does this fully resolve the concerns?

Comment thread src/core/views.py Outdated
Comment thread src/press/tests/test_views.py Outdated
Comment thread src/core/tests/test_views.py Outdated
Comment thread src/utils/logic.py Outdated
Comment thread src/utils/logic.py Outdated
Comment thread src/utils/orcid.py
@mauromsl mauromsl assigned joemull and unassigned mauromsl Feb 7, 2025
@joemull joemull requested a review from mauromsl February 11, 2025 14:40
@joemull joemull assigned mauromsl and unassigned joemull Feb 11, 2025

@mauromsl mauromsl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks Joe, I appreciate you bringing in my suggestions. I have one more and I'll be off your case here :P

Comment thread src/core/logic.py Outdated
Comment thread src/core/views.py Outdated
@mauromsl mauromsl assigned joemull and unassigned mauromsl Feb 11, 2025
@joemull joemull requested a review from mauromsl February 13, 2025 17:55
@joemull joemull assigned mauromsl and unassigned joemull Feb 13, 2025
@joemull joemull added this to the v1.8.0 Tracker milestone Feb 17, 2025

@mauromsl mauromsl left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

a comment regarding the issues around press URLs and tests

Comment thread src/core/templatetags/next_url.py Outdated
Comment on lines +35 to +32
else:
# This is the only way to refer to press URLs
next_url = request.build_absolute_uri()

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The problem exhibited in 39eebbc is related to how the test is setup. It uses an incomplete request object mockup rather than a real request, so there are no headers from where to borrow a URL path. I've changed it to use Django's test client and the test is passing now.
Does this fully resolve the concerns?

@mauromsl mauromsl assigned joemull and unassigned mauromsl Feb 18, 2025
@joemull joemull requested a review from ajrbyers February 20, 2025 12:44
@joemull joemull assigned mauromsl and ajrbyers and unassigned joemull and mauromsl Feb 20, 2025
@ajrbyers ajrbyers merged commit cdd9624 into master Feb 26, 2025
@ajrbyers ajrbyers deleted the 3899-next-urls branch February 26, 2025 10:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants