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

Fixed #30186 -- Made showmigrations --list display the applied datetimes at verbosity 2+. #10997

Merged
merged 1 commit into from Mar 8, 2019

Conversation

tim-schilling
Copy link
Contributor

When verbosity is 2 or greater and listing migrations with
showmigrations, include the applied datetime in the output.

Copy link
Member

@charettes charettes left a comment

Choose a reason for hiding this comment

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

Any thoughts about changing plan nodes to be three elements (app, name, applied=None) instead of using this custom tuple subclass and loosing the containment check abilities on the returned set?

return {tuple(x) for x in self.migration_qs.values_list('app', 'name')}
return {
AppliedMigration([m.app, m.name], applied=m.applied)
for m in self.migration_qs.iterator()
Copy link
Member

Choose a reason for hiding this comment

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

It don't think it's worth using iterator() here given the small size of the result set and the fact it will be fully materialized. Also you could keep using values_list('app', 'name', 'applied').

class AppliedMigration(tuple):
    def __init__(self, app, name, applied):
        instance = tuple.__new__(cls, (app, name))
        instance.applied = applied
        return instance

return {AppliedMigration(x) for x in self.migration_qs.values_list('app', 'name', 'applied')

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sounds good.

@@ -84,9 +86,15 @@ def show_list(self, connection, app_names=None):
title = plan_node[1]
if graph.nodes[plan_node].replaces:
title += " (%s squashed migrations)" % len(graph.nodes[plan_node].replaces)
# Get the applied migration instance
filtered = filter(lambda m: m == plan_node, loader.applied_migrations)
Copy link
Member

Choose a reason for hiding this comment

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

This was a problem before as well but I guess it should be fixed while you are around.

In the case where showmigrations is not called with an app label (or called with multiple ones) this will incur len(app_names) queries since each access to loader.applied_migrations incurs one. It might be worth assigning a applied_migrations = loader.applied_migrations variable outside of the loop.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can do.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Wait, loader.applied_migrations is not the same as Recorder.applied_migrations() where the latter actually makes a DB query. loader.applied_migrations is a property that doesn't make any DB queries, but gets set in build_graph()

Copy link
Member

Choose a reason for hiding this comment

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

Ahh never mind then, I assumed it was the same function based on the name.

@tim-schilling
Copy link
Contributor Author

Any thoughts about changing plan nodes to be three elements (app, name, applied=None) instead of using this custom tuple subclass and loosing the containment check abilities on the returned set?

@timgraham Brought up a similar issue. I implemented this way because there are a number of places where there are comparisons to the results of applied_migrations. Theses comparisons are done where the other element is from some other location that wouldn't have an applied datetime. For example, when checking to see if a migration was applied it, the disk migration would have (app, name, None) but applied_migrations() would return (app, name, datetime). The applied datetime is just additional information, but this would cause these checks to fail in their current state requiring me to re-write them.

There are three proposed options so far, this tuple subclass I have, Tim suggested returning the Migration model instances or changing this to a three element tuple as you suggested. A fourth option would be to return a dict where the key is the (app, name) tuple and the value is the Migration object. That would make the comparisons easier with disk migrations and avoid a weird subclass.

@charettes
Copy link
Member

Thanks for the summary @tim-schilling!

A fourth option would be to return a dict where the key is the (app, name) tuple and the value is the Migration object. That would make the comparisons easier with disk migrations and avoid a weird subclass.

I like this option a lot. It preserves the current the set'ish nature of applied_migration while exposing the actual migration object for .applied access.

@timgraham timgraham changed the title Show the applied datetime in verbose showmigrations. Fixed #30186 -- Allowed showmigrations --list to display the applied datetimes. Feb 20, 2019
Copy link
Member

@carltongibson carltongibson left a comment

Choose a reason for hiding this comment

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

Hi @tim-schilling.

This looks super. Thanks.

I just left a few minor comments. I'm not insistent on those...

Can you rebase and squash to a single commit?

I'm going to mark this RFC. Good stuff!

migration[0], migration[1], parent[0], parent[1],
connection.alias,
migration_key[0], migration_key[1], parent[0],
parent[1], connection.alias,
Copy link
Member

Choose a reason for hiding this comment

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

This could go on one line I think.

(Would be less than 120 chars and splitting parent[0] from parent[1] is a little weird.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ah, I thought the limit was 80 characters. I was wondering why there were so many offending lines.

return {tuple(x) for x in self.migration_qs.values_list('app', 'name')}
return {
(migration.app, migration.name): migration
for migration in self.migration_qs
Copy link
Member

Choose a reason for hiding this comment

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

Single line?

@@ -69,13 +69,16 @@ def ensure_schema(self):
raise MigrationSchemaMissing("Unable to create the django_migrations table (%s)" % exc)

def applied_migrations(self):
"""Return a set of (app, name) of applied migrations."""
"""Return a dict of (app, name), Migration of applied migrations."""
Copy link
Member

Choose a reason for hiding this comment

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

Maybe "Return a dict of (app, name) to Migration instances of applied migrations."

(Just not sure the comma reads...)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Works for me.

@@ -259,7 +262,7 @@ to remove usage of these features.
* Support for the ``context`` argument of ``Field.from_db_value()`` and
``Expression.convert_value()`` is removed.

* The ``field_name`` keyword argument of ``QuerySet.earliest()` and
* The ``field_name`` keyword argument of ``QuerySet.earliest()`` and
Copy link
Member

Choose a reason for hiding this comment

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

Merged this as 38c3f7e. Thanks!

@tim-schilling
Copy link
Contributor Author

Implemented your suggestions, squashed and rebased. Thanks for the review!

@timgraham
Copy link
Member

Could you send the refactoring code/test changes as a separate PR so that's not tied up with the feature change? Then we can rebase this and it'll look less invasive.

@tim-schilling
Copy link
Contributor Author

@timgraham I'm confused. I should have two PRs? One with the changes to django (with failing tests) and then the other with the tests for non-existing functionality that fail?

Or would the second include the functionality changes?

@timgraham
Copy link
Member

What I meant as "the refactoring" is all the changes for changing the return type of applied_migrations().

@tim-schilling
Copy link
Contributor Author

That was refactoring in order to make the feature possible. I can't separate them.

@timgraham
Copy link
Member

The refactoring can precede the feature, no?

@tim-schilling
Copy link
Contributor Author

Yes. Sorry, I (for some reason) assumed the order was the opposite. If that's what works best, I can break it up that way. What do I title the refactoring PR?

@timgraham
Copy link
Member

Refs #30186 -- Changed MigrationRecorder.applied_migrations() to return a dict.

@tim-schilling
Copy link
Contributor Author

Thanks for the help.

@tim-schilling
Copy link
Contributor Author

Did you want me to rebase this off of master without the commit from #11058?

@timgraham timgraham changed the title Fixed #30186 -- Allowed showmigrations --list to display the applied datetimes. Fixed #30186 -- Made showmigrations --list display the applied datetimes at verbosity 2+. Mar 8, 2019
@timgraham timgraham force-pushed the ticket_30186 branch 2 times, most recently from ef4f352 to c78b848 Compare March 8, 2019 01:04
@timgraham timgraham merged commit 7c68cea into django:master Mar 8, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
4 participants