-
Notifications
You must be signed in to change notification settings - Fork 397
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
Swappable models #389
Swappable models #389
Conversation
Do you mind cleaning up the lint errors so the Travis tests will run? Do we really need a to allow swapping all models? Or would handling just |
Lint should be good after 581d0c9 and 3debd40. About swapping all the models: in my use cases, I have custom models for I still need to figure out how to fix tests + migrations, but what do you think about this PR? |
@m-vdb don't upgrade to 0.8.7 or do it in test server. we just removed this feature in 0.8.7 |
@jdufresne Let review this change, we could be breaking other people code. |
I'll try to find some time to give this a deeper review. |
I'm very interested in this as I use base classes heavily, and I find it hard to maintain. |
@m-vdb Can you rebase this PR so Travis runs against the latest mater? I'd like to see CI green before the review continues. |
my branch starts from |
Oh you're right. Sorry, I misspoke. I intended to ask to rebase on develop. When you have a chance, can you do that and squash the commits? It would help keep the change tidy and aid reviewing. Nice, I think that the general approach looks good! I think the use of swappable will be easier to maintain than the previous base classes. I'd like to see more tests and have them passing before this is merge ready. The tests go a long way towards allowing me to analyze the change. Let me know when that is ready and I'll continue the review. It might help to look at how Django tests the swappable Nice work! |
except ImportError: | ||
raise RuntimeError(self.__doc__.strip()) | ||
|
||
@override_settings(SCHEDULER_CALENDAR_MODEL='schedule.Calendar', SCHEDULER_EVENT_MODEL='schedule.Event') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is this overridden to not be the alternate model? Shouldn't the new tests use the CustomEvent
class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had an error with it but it might be related to my migration issue
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ah I know why:
- the tests are executed within the context of the
tests
project, using thetests.settings
module - the migrations is meaningful only in the context of the
tests.test_project
, using thetests.test_project.settings
- as a consequence, importing the migration file like I do in the tests results in an import error: the
django.conf.settings.SCHEDULER_CALENDAR_MODEL
isn't defined fortests.settings
but it is defined fortests.test_project.settings
.travis.yml
Outdated
@@ -27,6 +27,7 @@ before_script: | |||
- isort --check-only --diff | |||
- python -m django check --settings=tests.settings | |||
- python -m django makemigrations --settings=tests.settings --no-input --dry-run --check | |||
- python -m django makemigrations test_project --settings=tests.test_project.settings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels a bit odd to me on the surface. Could this instead be a part of running the test? I'd like to avoid an extra test setup step if possible so that the tests are immediately runnable as before.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok I'll see what I can do, I just wanted to avoid executing a script within the tests. but maybe I can just use call_command()
, will try.
aa42c40
to
76aea1e
Compare
To resolve conflicts, do you mind rebasing instead of merging? It would make the commit history much cleaner and easier to review. Thanks. |
c7d09d3
to
4002ffe
Compare
done, sorry I missed that earlier. Here is where I am stuck: you can see that one of the tests that I introduced isn't passing. It is because I still haven't figured out how to migrate from the current version of Also, I realized something interesting. Once you define the swapped models, there are two migrations that are generated: one in the I've tried to search Django documentation and other projects GH issues and I've found similar issues, but I still don't know exactly how to fix that:
|
I've managed to make it somehow work, by following
Ideally, I think we would like to create the custom models as subclasses of the abstract models from the beginning, and generate a migration there, but this fails because of the following:
It's easily changeable on the models but it might introduce backwards-incompatible changes? Also, I think that defining the |
@jdufresne ok I think I'm good for all logical aspects, even for the migration. Tests are green now, can you take a look? Also, I think that this requires documentation and an extended migration guide to this new version; it shouldn't be too hard to write, I can do it before we merge this. |
def setUp(self): | ||
super(TestSwappableModels, self).setUp() | ||
# cleanup any possible leftovers before running | ||
self._cleanup() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why do you need to call _cleanup
both before and after? Shouldn't one be sufficient?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if for some reason the test completely crashes, this makes sure the test "setup" isn't corrupted. I can easily remove, it is just a nice-to-have for the developers of this repo.
@@ -1,7 +1,7 @@ | |||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) | |||
import os | |||
|
|||
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) | |||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was this change required to make the tests work? Can you explain?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
see 8c45306 : __file__
is only absolute in python3, so it was never defined right in python2. This had no consequences before because this settings wasn't used throughout the tests but now it is.
@@ -9,3 +9,5 @@ | |||
/dist/ | |||
*.egg-info/ | |||
/docs/_build/ | |||
# generated in tests, shouldn't be versionned | |||
tests/test_project/migrations/0001_initial.py |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't understand why this is in gitignore. Shouldn't this exist as a normal file?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the whole purpose of the test I wrote is to generate this file on the fly and verify that it is well formatted. If for some reason in the future someone inadvertently alters the shape of the generated file by changing the code of swappable models, then we will know it and the unit tests will break. Versioning this file doesn't make sense because it is generated by the tests. To avoid confusion for this repo's collaborators I chose to add this file to gitignore. If the test ever fails and the cleanup phase of the test fails to execute too, this file won't be visible with git status
and a collaborator won't risk versioning it.
tests/test_project/settings.py
Outdated
|
||
BASE_DIR = os.path.dirname(os.path.dirname(__file__)) | ||
|
||
SECRET_KEY = '_nc9_+ntllx!ptq5tzh96nn^$a#81#auhrdw#8t#+n0#4ig(lz' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Per 8343dde, please use an obviously fake secret for tests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
8fd293d ✅
tests/test_swapped_models.py
Outdated
""" | ||
popen = subprocess.Popen( | ||
[ | ||
'python', |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should probably use sys.executable
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1391f25 ✅
'--settings=tests.test_project.settings' | ||
], | ||
stdout=subprocess.PIPE, | ||
stderr=subprocess.PIPE, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you're not using the input/output, you can use DEVNULL
instead of PIPE
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in self._check_command_return()
I do. In case the command exited with a non-zero return code, this method will display the stdout
and stderr
of the command, see:
9661a38
to
1391f25
Compare
@jdufresne Is this still awaiting anything before a merge? The base class is key, in my opinion, to address the problem of extensibility while avoiding the problems #377 |
UPDATE - This goes away with specifying the settings variables for the @m-vdb - I am testing a few things with this branch and tried inheriting from the various I noticed that I am getting a reverse accessor name clash. However, if the special related_name syntax is used, this goes away - as per this section in the Django docs
|
Onto one of the final steps now, and I noticed that one of the migrations seems not to recognize the derived name of the
Upon inspection, this was due to the initial migrations for the However, trying to create the initial migrations for the app results in pointing back to the
|
Perhaps splitting up the app might help alleviate the migration issues - as in this PR for vera. I'm not familiar enough with the swappable migrations, but I'll try to fix based on this - |
@@ -16,7 +16,7 @@ def feed_title(self, obj): | |||
return "Upcoming Events for %s" % obj.name | |||
|
|||
def get_object(self, request, calendar_id): | |||
return Calendar.objects.get(pk=calendar_id) | |||
return get_calendar_model().objects.get(pk=calendar_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of calling get_calendar_model() directly 2 times you can add Calendar = get_calendar_model()
.
General suggestion: I think the code will be cleaner if you use same approach for get_calendar_model()
, get_event_model()
and get_occurrence_model()
for all your changes.
|
||
def test_get_calendar_model_swapped_bad_settings(self): | ||
with override_local_settings(SCHEDULER_CALENDAR_MODEL='badsetting'): | ||
with self.assertRaisesMessage(ImproperlyConfigured, "SCHEDULER_CALENDAR_MODEL must be of the form 'app_label.model_name'"): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line too long https://www.python.org/dev/peps/pep-0008/#maximum-line-length
Same for lines: 169, 178, 183, 192, 197
and then swap its models. | ||
""" | ||
settings_path = os.path.join(settings.BASE_DIR, 'tests', 'test_project', 'settings.py') | ||
updated_settings_path = os.path.join(settings.BASE_DIR, 'tests', 'test_project', 'updated_settings.py.tpl') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line to long https://www.python.org/dev/peps/pep-0008/#maximum-line-length
Same for lines: 74, 104, 131, 143
@@ -16,7 +16,7 @@ def feed_title(self, obj): | |||
return "Upcoming Events for %s" % obj.name | |||
|
|||
def get_object(self, request, calendar_id): | |||
return Calendar.objects.get(pk=calendar_id) | |||
return get_calendar_model().objects.get(pk=calendar_id) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of calling get_calendar_model() directly 2 times you can add Calendar = get_calendar_model()
.
General suggestion: I think the code will be cleaner if you use same approach for get_calendar_model()
, get_event_model()
and get_occurrence_model()
for all your changes.
occurrence_id = get_kwarg_or_param(request, kwargs, 'occurrence_id') | ||
return Occurrence.objects.filter(pk=occurrence_id).first() if occurrence_id else None | ||
return get_occurrence_model().objects.filter(pk=occurrence_id).first() if occurrence_id else None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Line too long https://www.python.org/dev/peps/pep-0008/#maximum-line-length
Same for lines: 117, 127, 224, 238, 243, 257
@@ -218,7 +228,7 @@ class CalendarRelation(models.Model): | |||
may not scale well. If you use this, keep that in mind. | |||
''' | |||
|
|||
calendar = models.ForeignKey(Calendar, on_delete=models.CASCADE, verbose_name=_("calendar")) | |||
calendar = models.ForeignKey(SCHEDULER_CALENDAR_MODEL, on_delete=models.CASCADE, verbose_name=_("calendar")) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
name: the name of the calendar | ||
events: all the events contained within the calendar. | ||
>>> calendar = Calendar(name = 'Test Calendar') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
calendar = Calendar(name='Test Calendar')
Don't use spaces around the = sign when used to indicate a keyword argument or a default parameter value. https://www.python.org/dev/peps/pep-0008/#other-recommendations
@@ -104,46 +104,18 @@ def get_calendars_for_object(self, obj, distinction=''): | |||
|
|||
|
|||
@python_2_unicode_compatible | |||
class Calendar(models.Model): | |||
class AbstractCalendar(models.Model): | |||
''' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's not your line, but could you please replace ''' with """ in docstring?
https://www.python.org/dev/peps/pep-0008/#documentation-strings
@@ -47,10 +47,10 @@ def get_for_object(self, content_object, distinction='', inherit=True): | |||
|
|||
|
|||
@python_2_unicode_compatible | |||
class Event(models.Model): | |||
class AbstractEvent(models.Model): | |||
''' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I know it's not your line, but could you please replace ''' with """ in docstring?
https://www.python.org/dev/peps/pep-0008/#documentation-strings
return event | ||
|
||
|
||
def get_calendar(event, request, **kwargs): | ||
from schedule.models import Calendar | ||
calendar = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's not your line, but could you please remove it? I think it's pointless.
Thank you everyone for comments and reviews on this pull request. |
Sorry, @llazzaro. I won't have time in the near future to review this. If it looks good to you, please proceed. |
Is this going to merge any time soon? (Thanks for the great work, everyone!) |
What can I do to move this forward? I need to customize the occurrence model and would prefer to base that on the common project code. :) @llazzaro |
any updates on this? |
I am no longer using |
Fixes #306
This PR aims at proposing a powerful replacement for base classes that were removed in #377.
⚠️ Do not merge for now, we need to test the version change on live projects
Changes summary:
get_user_model()
)