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

Fixes #96: Improve string representation of sortedm2m relationships #101

Merged
merged 2 commits into from
Aug 1, 2017

Conversation

rohithasrk
Copy link
Contributor

Fixes #96

This PR aims to improve the string representation of SortedM2M relationship when deleting one from an admin panel. I basically added a base_class parameter, a class which is inherited by the through-model of a sortedm2m relationship. This is basically so that the users of django-sortedm2m can add their own __unicode__ method and improve the string representation.

For ex., in example/testapp/models.py we've passed a base_class argument when a relationship is being established. Check the image below,

sortedm2m

@gregmuellegger : Please review.

Copy link
Contributor

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

Hey @rohithasrk. great work!

I left you comments on parts that need improvement.

TODO

In order to improve the pull request so it can be merge you should:

  • address the inline comments
  • add at least an assertion in the test suite that checks the m2mprint model __str__ method
  • squash your commits into one
  • add a reference to this new feature in the README (a few lines of text are enough, don't worry about the english grammar/style, I will help you, just show that you are willing to put effort into this)

Automated tests

Regarding the tests, I suggest to add an assertion here:

https://github.com/gregmuellegger/django-sortedm2m/blob/master/sortedm2m_tests/test_field.py

This means you should probably move your new model to this file:

https://github.com/gregmuellegger/django-sortedm2m/blob/master/sortedm2m_tests/models.py

@@ -9,10 +9,13 @@ class Car(models.Model):
def __unicode__(self):
return self.plate

Copy link
Contributor

Choose a reason for hiding this comment

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

follow pep8 conventions and add an additional blank line

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Shall I use tools like autopep8 to correct all pep8 errors?

Copy link
Contributor

Choose a reason for hiding this comment

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

In OpenWISP we use flake8 and it is enforced in the build, but not sure is enforced here. For the moment I suggest you to focus on fixing these small issues I pointed out, it will be easier for you.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Okay. Thanks :)

@@ -9,10 +9,13 @@ class Car(models.Model):
def __unicode__(self):
return self.plate

class m2mprint:
def __unicode__(self):
Copy link
Contributor

@nemesifier nemesifier Mar 20, 2017

Choose a reason for hiding this comment

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

__unicode__ is deprecated (in python 3 there's only unicode strings). Use __str__ with django.utils.encoding.python_2_unicode_compatible.

Subclass the django base model class.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

__unicode__ method is being used almost everywhere in this package. Shall I replace all the occurrences?

Copy link
Contributor

Choose a reason for hiding this comment

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

@rohithasrk I'm looking now.. wait a minute and I'll come back to you regarding this

Copy link
Contributor

@nemesifier nemesifier Mar 21, 2017

Choose a reason for hiding this comment

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

I suggest you to move this new model to:
https://github.com/gregmuellegger/django-sortedm2m/blob/master/sortedm2m_tests/models.py

It will be easier for you to import that model in the test suite.

In the test suite you will have to ensure the representation of the model is what you expect and I'm pretty confident declaring __unicode__ only would not work on python3.
I leave the solution up to you, as long as the test works, I'm fine with it.

I pointed out that solution because in the OpenWISP modules I try to use python_2_unicode_compatible as much as possible but this package was probably developed before this utility was added to django.

@@ -212,17 +212,23 @@ class SortedManyToManyField(_ManyToManyField):
'''
Providing a many to many relation that remembers the order of related
objects.

Copy link
Contributor

Choose a reason for hiding this comment

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

you are adding spaces and changing a line that doesn't need to be changed, try to revert this change (insert a blank line without space, commit and then squash/fixup).

if self.base_class:
return type(str(name), (models.Model, self.base_class), attrs)
else:
return type(str(name), (models.Model,), attrs)
Copy link
Contributor

Choose a reason for hiding this comment

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

this whole if block can be improved. You are repeating str(name), (models.Model,) and attrs twice.

You could store that tuple in an auxiliary variable and insert the base class if needed.

Could you try this? You could also try a different idea if you like.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. Seems good.


def create_intermediate_model(self, klass):
# Construct and return the new class.
from_field_name, from_field = self.get_intermediate_model_from_field(klass)
to_field_name, to_field = self.get_intermediate_model_to_field(klass)
sort_value_field_name, sort_value_field = self.get_intermediate_model_sort_value_field(klass)

meta = self.get_intermediate_model_meta_class(
Copy link
Contributor

Choose a reason for hiding this comment

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

more readable would be:

meta = self.get_intermediate_model_meta_class(klass,
                                              from_field_name,
                                              to_field_name,
                                              sort_value_field_name)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes. I agree

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Using tools like autopep8 should help doing all these. Anyways, will fix them soon.

meta = self.get_intermediate_model_meta_class(
klass, from_field_name, to_field_name, sort_value_field_name)

Copy link
Contributor

Choose a reason for hiding this comment

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

you are adding spaces and changing a line that doesn't need to be changed, try to revert this change (insert a blank line without space, commit and then squash/fixup).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My bad. Will fix them

@rohithasrk
Copy link
Contributor Author

Thanks for the review @nemesisdesign. I'll update the PR soon

@nemesifier
Copy link
Contributor

Regarding the tests, I suggest to add an assertion here:

https://github.com/gregmuellegger/django-sortedm2m/blob/master/sortedm2m_tests/test_field.py

This means you should probably move your new model to this file:

https://github.com/gregmuellegger/django-sortedm2m/blob/master/sortedm2m_tests/models.py

I will copy this text in the review text.

Federico

@rohithasrk rohithasrk force-pushed the str-rep branch 2 times, most recently from aedd21a to fc511ab Compare March 24, 2017 11:36
@rohithasrk
Copy link
Contributor Author

@nemesisdesign : I've updated the PR by squashing the commits and modifying the commits with the changes you've suggested. Please review. Thanks

@nemesifier
Copy link
Contributor

nemesifier commented Mar 24, 2017

@rohithasrk great! There's only one thing missing, an assertion in the test suite. Did you get one of my last replies?

I see that you have created a model in the testapp but that is not enough. We should ensure that the test model is behaving as expected.

@nemesifier
Copy link
Contributor

@gregmuellegger I think this PR is going to be ready soon

@rohithasrk
Copy link
Contributor Author

@nemesisdesign : Added a test case too. I'm kinda new to writing tests, so please review. Thank you

@rohithasrk
Copy link
Contributor Author

This test seems to be giving wrong results. I'll update the revised version in a while

@rohithasrk
Copy link
Contributor Author

@nemesisdesign : I think this is fine. I've written a test comparing the __unicode__ methods and not the outputs of instances. This just checks if the the __unicode__ methods of the m2mprint and shelf_books is the same.

@rohithasrk
Copy link
Contributor Author

@nemesisdesign @gregmuellegger I'm curious. Can this be merged?

Copy link
Contributor

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

@rohithasrk I can see you don't have a lot of experience with unit testing in general. No worries, I left you some comments that give you precise instructions on what to do. Please read carefully my comment and try to understand why I left them, if you have questions, please don't hesitate.

Implement these changes and you are done with this.

I'll ping @gregmuellegger to merge this as soon as he can.

class m2mprint:

def __unicode__(self):
return unicode(self.book)
Copy link
Contributor

@nemesifier nemesifier Mar 27, 2017

Choose a reason for hiding this comment

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

Please do something like the following:

class BaseCarThrough(object):
    def __str__(self):
        return "Relationship to {0}".format(self.book.name)

    def __unicode__(self):
        return u"{0}".format(str(self))

Copy link
Contributor Author

@rohithasrk rohithasrk Mar 27, 2017

Choose a reason for hiding this comment

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

This throws and error saying the maximum recursion depth has exceeded. Which IMO, is because str(self) is not invoking the __str__ method. It is calling the __unicode__ method. @nemesisdesign

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So, it's better to write them independently IMO. What say @nemesisdesign?

Copy link
Contributor

Choose a reason for hiding this comment

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

yes whatever it works as long as there is an assertion in the test that checks for the result is what we expect and the assertion works with python2 and python3.

class Shelf(models.Model):
books = SortedManyToManyField('Book', related_name='shelves')
books = SortedManyToManyField('Book', related_name='shelves', base_class=m2mprint)
Copy link
Contributor

@nemesifier nemesifier Mar 27, 2017

Choose a reason for hiding this comment

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

change m2mprint to BaseBookThrough

shelf_book = shelf.books.through

if hasattr(shelf_book, '__unicode__'):
self.assertEqual(shelf_book.__unicode__, m2mprint.__unicode__)
Copy link
Contributor

Choose a reason for hiding this comment

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

@rohithasrk, this is not good enough, you should do something like:

self.assertEqual(str(shelf), ''Relationship to {0}".format(shelf.name)

get rid of shelf_book = shelf.books.through.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Shouldn't it be shelf.book.name? As shelf doesn't have an attribute name?

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nemesisdesign : shelf.book.name throws errors too, as shelf has no attribute book. That is why I've used shelf_book = shelf.books.through.

Copy link
Contributor

Choose a reason for hiding this comment

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

apply the suggestions I suggested to you on IRC.

Do something like:

relationship = shelf_book.objects.first()
self.assertEqual(str(relationship), "<expected representation of the relationship here>")

if hasattr(shelf_book, '__unicode__'):
self.assertEqual(shelf_book.__unicode__, m2mprint.__unicode__)
else:
self.assertTrue(True)
Copy link
Contributor

Choose a reason for hiding this comment

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

Get rid of the if/else condition please.

BTW: adding stuff like self.assertTrue(True) is really bad. Don't do it!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nemesisdesign, I added these if/else conditions just to check if the base_class parameter has been provided. Tests fail when base_class parameter isn't provided. It shouldn't be the case right?

Copy link
Contributor

Choose a reason for hiding this comment

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

I have not understood your last comment very well.
You have to write a test which checks the output of the __str__ method is what you expect. No need to add if/else blocks.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nemesisdesign : I mean, should the test fail if a sortedm2m relationship doesn't have a base_class parameter? In my opinion, No.

As in the above mentioned changes, the test is failing. And that is why, I've used if/else conditions.

Copy link
Contributor

Choose a reason for hiding this comment

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

why should the test fail if you defined it in the model? Am I missing something?

@@ -10,9 +10,15 @@ def __unicode__(self):
return self.plate


class m2mprint:
Copy link
Contributor

Choose a reason for hiding this comment

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

shelf_book = shelf.books.through

if hasattr(shelf_book, '__unicode__'):
self.assertEqual(shelf_book.__unicode__, m2mprint.__unicode__)
Copy link
Contributor

Choose a reason for hiding this comment

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

apply the suggestions I suggested to you on IRC.

Do something like:

relationship = shelf_book.objects.first()
self.assertEqual(str(relationship), "<expected representation of the relationship here>")

if hasattr(shelf_book, '__unicode__'):
self.assertEqual(shelf_book.__unicode__, m2mprint.__unicode__)
else:
self.assertTrue(True)
Copy link
Contributor

Choose a reason for hiding this comment

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

why should the test fail if you defined it in the model? Am I missing something?

class m2mprint:

def __unicode__(self):
return unicode(self.book)
Copy link
Contributor

Choose a reason for hiding this comment

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

yes whatever it works as long as there is an assertion in the test that checks for the result is what we expect and the assertion works with python2 and python3.

Copy link
Contributor

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

I believe this is the last round of changes!

def test_base_class_str(self):
shelf = self.model.objects.create()
shelf.books.add(self.books[0])

Copy link
Contributor

Choose a reason for hiding this comment

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

remove blank line


through_model = shelf.books.through
instance = through_model.objects.first()

Copy link
Contributor

Choose a reason for hiding this comment

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

remove blank line

@@ -10,9 +10,15 @@ def __unicode__(self):
return self.plate


class BaseCarThrough(object):

Copy link
Contributor

Choose a reason for hiding this comment

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

remove blank line

shelf.books.add(self.books[0])

through_model = shelf.books.through
instance = through_model.objects.first()
Copy link
Contributor

Choose a reason for hiding this comment

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

unfortunately I forgot first() is not supported on old django versions.

Try rewriting this line as:

instance = through_model.objects.all()[0]

@rohithasrk rohithasrk force-pushed the str-rep branch 2 times, most recently from 81adf42 to 3279680 Compare March 28, 2017 11:27
Copy link
Contributor

@nemesifier nemesifier left a comment

Choose a reason for hiding this comment

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

@gregmuellegger I think it's ready to be merged.

@rohithasrk
Copy link
Contributor Author

rohithasrk commented Jun 13, 2017

@gregmuellegger Resolved the conflicts and pushed. I think this can be merged.

@gregmuellegger gregmuellegger merged commit 2df098c into jazzband:master Aug 1, 2017
@gregmuellegger
Copy link
Collaborator

I'm deeply sorry for this contribution to being so long on the waiting! I was deeply dived into personal projects (like getting maried 💒) which was that I spent nearly no time in front of a computer.

Thank you for your patience and for the contribution and the amazing guidance that you @nemesisdesign gave here!

I will release this feature in a new release in the next days.

@rohithasrk rohithasrk deleted the str-rep branch August 1, 2017 19:23
@gregmuellegger
Copy link
Collaborator

It is released! You can find your contribution as part of: https://pypi.python.org/pypi/django-sortedm2m

@rohithasrk
Copy link
Contributor Author

Thank you @gregmuellegger

@nemesifier
Copy link
Contributor

Thanks for merging and congratulations for your marriage!

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.

3 participants