Save 37% off PRO during our Black Friday Sale! »

ビバLTS!既存プロジェクトをDjango1.11にバージョンアップした際のハマりどころ

 ビバLTS!既存プロジェクトをDjango1.11にバージョンアップした際のハマりどころ

PYCON JP 2017 ポスターセッション / 2017.9.9

6c3d8766f1f09e8b1088bbd9933b4b26?s=128

Yusuke Mukoyama

September 11, 2017
Tweet

Transcript

  1. Ϗό-54ʂ طଘϓϩδΣΫτΛ%KBOHPʹ όʔδϣϯΞοϓͨ͠ࡍͷϋϚΓͲ͜Ζ Ϝί΢ϠϚ Ϣ΢εέ 5XJUUFS!NVLPZBN $SFEJU&OHJOF *OD

  2. %KBOHP֓ཁ ग़యIUUQTXXXEKBOHPQSPKFDUDPNEPXOMPBE IUUQTEPDTEKBOHPQSPKFDUDPNFOSFMFBTFT ϦϦʔεϊʔτུ֓  1ZUIPOΛެࣜʹαϙʔτ͢Δ࠷ޙͷόʔδϣϯ ࣍ͷ%KBOHP͸ɺ1ZUIPO Λαϙʔτ  .PEFMʹ

    .FUBJOEFYFT Φϓγϣϯ͕௥Ճ͞ΕɺJOEFYͷγϯϓϧͳࢦఆํ๏͕௥Ճ  'PSNͷXJEHFU͕5FNQMBUFϕʔεͰ࣮૷͞ΕͨɻΧελϚΠζָ͕ʹɻʢEKBOHPGPSNTXJEHFU಺ͷ͍͔ͭ͘ͷΫϥε͸࡟আʣ  .PEFMʹ͓͚Δɺ໌ࣔతͳ4VCRVFSZ͕࢖༻Մೳʹɻ0VUFS3FG &YJTUT ͳͲͷදݱɻ  QZU[͕૊Έࠐ·ΕͨɻTFUUJOHT5*.&@;0/& /POF͸ແ͘ͳΔ  1PTUHSFT ͕αϙʔτର৅ʹɻQTZDPQH΋ɺ ʹɻ  $IBS'JFMEFNQUZ@WBMVF ʹΑͬͯɺFNQUZTUSJOHΛදݱՄೳʹ  MPBEEBUB FYDMVEFͰɺಡΈࠐΉGJYUVSFͷআ֎͕Ͱ͖ΔΑ͏ʹɻ  *NBHF'JFME͸ɺσϑΥϧτͰWBMJEBUF@JNBHF@GJMF@FYUFOTJPO όϦσʔλΛ࢖༻ 'JMF&YUFOTJOP7BMJEBUPS WBMJEBUF@JNBHF@GJMF@FYUFOTJPO͕௥Ճ͞Εͨ  MPHJO MPHPVU ͷؔ਺ϕʔεϏϡʔ͸ඇਪ঑ʹɻ -PHJO7JFXͱ-PHPVU7JFX͕৽ઃ  FUD αϙʔτεέδϡʔϧɾϩʔυϚοϓ ͸ɺ·Ͱͷ೥ؒͷ-POHUFSNTVQQPSU -54 ʹɺͷϦϦʔε༧ఆ -54ޙ͸ɺ࣍ͷʮYʯʹඈͿ
  3. ΁ͷҠߦ ֎෦Ϟδϡʔϧͷௐࠪɾमਖ਼ ࠷΋͙͢ʹӨڹ͕ग़ͦ͏ͳͱ͜Ζ͸ɺΛαϙʔτ͍ͯ͠ͳ͍֎෦Ϟδϡʔϧʢ΁ͷҠߦʹݶΒ͕ͣͩɻʣ %KBOHPͱґଘؔ܎ʹ͋Δɺ͔ͭɺҎ্Λαϙʔτ͍ͯ͠ͳ͍ ֎෦ϞδϡʔϧΛ·ͣ͸ௐࠪ͢Δɻ naiquevin/pipdeptree ͷར༻ɻ A command line

    utility to display dependency tree of the installed Python packages. $ pipdeptree (抜粋) ------------------------------------------------------------------------------------------ django-multiupload==0.5.2 - django [required: Any, installed: 1.11] - pytz [required: Any, installed: 2016.7] django-rest-auth==0.8.2 - Django [required: >=1.8.0, installed: 1.11] - pytz [required: Any, installed: 2016.7] - djangorestframework [required: >=3.1.0, installed: 3.5.1] - six [required: >=1.9.0, installed: 1.10.0] django-twilio==0.8.0 - Django [required: >=1.4, installed: 1.11] - pytz [required: Any, installed: 2016.7] - django-phonenumber-field [required: >=0.6, installed: 1.3.0] - babel [required: Any, installed: 2.4.0] - pytz [required: >=0a, installed: 2016.7] - django [required: >=1.5, installed: 1.11] - pytz [required: Any, installed: 2016.7] - phonenumberslite [required: >=7.0.2, installed: 8.4.2] - twilio [required: >=3.6.9, installed: 5.6.0] - httplib2 [required: >=0.7, installed: 0.10.3] - pysocks [required: Any, installed: 1.6.7] - pysocks [required: Any, installed: 1.6.7] - pytz [required: Any, installed: 2016.7] - six [required: Any, installed: 1.10.0] Warning!!! Possibly conflicting dependencies found: * django-rest-swagger==2.1.1 - djangorestframework [required: >=3.5.3, installed: 3.5.1] %KBOHPʹόʔδϣϯΞοϓޙɺQJQEFQUSFFΛ࣮ߦͯ͠ΈΔͱ ҙ֎ͱ%KBOHPͱґଘؔ܎ʹ͋ΔϞδϡʔϧ͸গͳ͔ͬͨɻ Chive/django-multiupload Tivix/django-rest-auth rdegges/django-twilio ʹ͍ͭͯ͸ɺυΩϡϝϯτΛ֬ೝ͕ͨ͠ɺͺͬͱݟͰ͸໰୊ͳͦ͞͏ɻ 8BSOJOH͕ग़͍ͯΔ෦෼͸ɺTXBHHFSͷཁٻ͢Δɺ EKBOHPSFTUGSBNFXPSL ͷόʔδϣϯ͕௿͍Α͏ͳͷͰ࠷৽ʹߋ৽ɻ ͦͷଞɺϦϦʔεϊʔτʹهࡌ͞Ε͍ͯͨɺQTZDPQHͷόʔδϣϯ͕ Ҏ্͔͸֬ೝࡁΈɻ ͔͠͠ɺىಈͯ͠Έͨͱ͜ΖɺΤϥʔ͕ൃੜɻ $ ./manage.py runserver (抜粋)… ImportError: Could not import 'django_filters.rest_framework.DjangoFilterBackend' for API setting 'DEFAULT_FILTER_BACKENDS'. ImportError: cannot import name 'flatatt'.
  4. ΁ͷҠߦ ֎෦Ϟδϡʔϧͷௐࠪɾमਖ਼ carltongibson/django-filter ͷόʔδϣϯΛ࠷৽ʹ͋͛ͯΈͨͱ͜Ζɺͻͱ·ͣ໰୊͸ղফɻ EKBOHPGJMUFSͷΞοϓσʔτ΋ϦεΫ͸ߴͦ͏͕ͩɺࣾ಺༻"1*ͷॏཁ౓͕௿͍ػೳͷΈͰͷར༻ͳͷͰɺ໨ΛͭͿͬͯΞοϓσʔτɻɻ https://django-filter.readthedocs.io/en/develop/guide/migration.html The 1.0 release of

    django-filter introduces several API changes and refinements that break forwards compatibility. Below is a list of deprecations and instructions on how to migrate to the 1.0 release. A forwards-compatible 0.15 release has also been created to help with migration. It is compatible with both the existing and new APIs and will raise warnings for deprecated behavior. 5FNQMBUFͷϨϯμϦϯάϝιουมߋ ඇޙํޓ׵ͷΞοϓσʔτʹ্͕͍ͬͯͨɺ5FNQMBUFϨϯμϦϯάϝιουͷมߋͰΤϥʔ͕ൃੜ͍ͯͨ͠ɻ >django.template.backends.django.Template.render() prohibits non-dict context > For compatibility with multiple template engines, django.template.backends.django.Template.render() (returned from high-level template loader APIs > such as loader.get_template()) must receive a dictionary of context rather than Context or RequestContext. If you were passing either of the two > classes, pass a dictionary instead – doing so is backwards-compatible with older versions of Django. ্ه͸ผͷมߋ఺Ͱ͋Δʮ+JOKBͷDPOUFYU@QSPDFTTPSTαϙʔτʯʹ΋ؔ܎͢Δมߋ఺ͱࢥΘΕΔɻ The Jinja2 template backend now supports context processors by setting the 'context_processors' option in OPTIONS. Լهͷ௨ΓɺUFNQMBUFSFOEFSʹର͢Δ$POUFYUΫϥεͷར༻Λमਖ਼ͨ͠ɻ # sample.py from django.template import Context, loader context = Context({ "val1" : val1, "val2" : val2 }) t = loader.get_template(template) html = t.render(context=context) 0ME # sample.py from django.template import loader context = { "val1" : val1, "val2" : val2 } t = loader.get_template(template) html = t.render(context=context) /FX
  5. ΁ͷҠߦ 4FMFDU%BUF8JEHFUͷ࣮૷มߋ GPSNT4FMFDU%BUF8JEHFUΛPWFSSJEF͠ɺҰ෦ͷ੍໿৚݅Λมߋͨ͠͏͑Ͱར༻͢Δ࣮૷Λߦ͍͕ͬͯͨɺ όʔδϣϯΞοϓʹ൐͍ɺ4FMFDU%BUF8JEHFUΫϥεͷ࣮૷͕ϝιουΛͷ಺༰ΛؚΊେ෯ʹमਖ਼͞Ε͍ͯͨɻ Ҏલͷํ͕͔ͳΓεοΩϦ͍ͯͨ͠ͷ͕ͩɺXJEHFUपΓͷ࢓༷มߋͰɺͰ͸͜ͷΑ͏ͳ࣮૷ʹͳͬͨͱࢥΘΕΔɻ DSFBUF@TFMFDUɺSFOEFSϝιου͕ͳ͘ͳΓɺHFU@DPOUFYUɺGPSNBU@WBMVFϝιου͕৽ͨʹ࣮૷ɻ ͷ࣮૷ʹ߹Θͤɺ࠶౓PWFSSJEFͰ࣮૷Λ͓͜ͳͬͨɻ DSFBUF@TFMFDU ͱHFU@DPOUFYU ͕χΞϦʔΠίʔϧ

    def create_select(self, name, field, value, val, choices, none_value): if 'id' in self.attrs: id_ = self.attrs['id'] else: id_ = 'id_%s' % name if not self.is_required: choices.insert(0, none_value) local_attrs = self.build_attrs(id=field % id_) s = self.select_widget(choices=choices) select_html = s.render(field % name, val, local_attrs) return select_html  month_choices = list(self.months.items()) if not self.is_required: month_choices.insert(0, self.month_none_value) month_attrs = context['widget']['attrs'].copy() month_name = self.month_field % name month_attrs['id'] = 'id_%s' % month_name date_context['month'] = self.select_widget(attrs, choices=month_choices).get_context( name=month_name, value=context['widget']['value']['month'], attrs=month_attrs, ) day_choices = [(i, i) for i in range(1, 32)] if not self.is_required: day_choices.insert(0, self.day_none_value) day_attrs = context['widget']['attrs'].copy() day_name = self.day_field % name day_attrs['id'] = 'id_%s' % day_name date_context['day'] = self.select_widget(attrs, choices=day_choices,).get_context( name=day_name, value=context['widget']['value']['day'], attrs=day_attrs, ) subwidgets = [] for field in self._parse_date_fmt(): subwidgets.append(date_context[field]['widget']) context['widget']['subwidgets'] = subwidgets return context def get_context(self, name, value, attrs): context = super().get_context(name, value, attrs) date_context = {} year_choices = [(i, str(i)) for i in self.years] if not self.is_required: year_choices.insert(0, self.year_none_value) year_attrs = context['widget']['attrs'].copy() year_name = self.year_field % name year_attrs['id'] = 'id_%s' % year_name date_context['year'] = self.select_widget(attrs,choices=year_choices).get_context( name=year_name, value=context['widget']['value']['year'], attrs=year_attrs, )  ͖ͭͮ
  6. ΁ͷҠߦ EKBOHPEFCVHUPPMCBSͷόά 'JYFE # settings.py DEBUG_TOOLBAR_CONFIG = { # Add

    in this line to disable the panel 'DISABLE_PANELS': { 'debug_toolbar.panels.templates.TemplatesPanel', 'debug_toolbar.panels.redirects.RedirectsPanel', }, } EKBOHPEFCVHUPPMCBSར༻࣌ʹɺXJEHFUͷϨϯμϦϯά͕਺ेඵ஗Ԇͯ͠͠·͏ EKBOHPEFCVHUPPMCBSʹͯमਖ਼ࡁΈ 5FNQPSBSZ4PMVUJPO
  7. ओཁ৽ػೳͷར༻ .FUBJOEFYFT όʔδϣϯΞοϓޙʹ.PEFMΛ࣮૷ɺ*OEFYΛར༻͢ΔࡍʹɺͰ৽ػೳͱ࣮ͯ͠૷͞Εͨ .FUBJOEFYFTΛར༻͢Δɻ ΠϯσοΫεΛద༻͢ΔϑΟʔϧυΛMJTUͰهࡌ͢Δදݱ͕ՄೳʹͳΓɺݟ௨͕͠Α͘ศརʹͳͬͨɻ ௨ৗ͸ঢॱͷΠϯσοΫεɺϋΠϑϯΛ෇༩͢Δ͜ͱʹΑΓɺ߱ॱͷΠϯσοΫεʹͳΔɻ # models.py from django.db

    import models class SampleModel(models.Model): field1 = models.IntegerField(db_index=True) field2 = models.IntegerField() field3 = models.IntegerFIeld(db_index=True)  # models.py from django.db import models class SampleModel(models.Model): field1 = models.IntegerField() field2 = models.IntegerField() field3 = models.IntegerField() class Meta: indexes = [ models.Index(fields=["field1", "-field3”]) ]  5FNQMBUFCBTFEXJEHFUSFOEFSJOH XJHFUͷ5FNQMBUFʹؔΘΔ෦෼ͷ࣮૷͕ɺ%KBOHPͷ5FNQMBUFΤϯδϯΛݩʹͨ͠΋ͷʹͳΓɺΧελϚΠζੑ΍ ݟ௨͕͠Α͘ͳͬͨɻUFNQMBUFϑΝΠϧͱͯ͠ผ్੾Γग़࣮ͯ͠૷Λߦ͏͜ͱ͕Ͱ͖Δɻ  # django/forms/widget.py class Textarea(Widget): template_name = 'django/forms/widgets/textarea.html' def __init__(self, attrs=None): # Use slightly better defaults than HTML's 20x2 box default_attrs = {'cols': '40', 'rows': '10'} if attrs: default_attrs.update(attrs) super().__init__(default_attrs)  # django/forms/widget.py class Textarea(Widget): def __init__(self, attrs=None): # Use slightly better defaults than HTML's 20x2 box default_attrs = {'cols': '40', 'rows': '10'} if attrs: default_attrs.update(attrs) super(Textarea, self).__init__(default_attrs) def render(self, name, value, attrs=None): if value is None: value = '’ final_attrs = self.build_attrs(attrs, name=name) return format_html('<textarea{}>\r\n{}</textarea>', flatatt(final_attrs), force_text(value)) # django/forms/widget/textarea.html <textarea name="{{ widget.name }}"{% include "django/forms/widgets/attrs.html" %}> {% if widget.value %}{{ widget.value }}{% endif %}</textarea>
  8. %KBOHP ओཁػೳ֓ཁ  1ZUIPOҎ্Λαϙʔτʢܥ͸͞ΑͳΒʣ  ͦΕʹΑͬͯɺCZUFTUSJOH ͷαϙʔτ੍͕ݶ͞ΕΔʢҰ෦Ͱʁʣ  TMJDFͷ͋ͱͷɺ2VFSZ4FUSFWFSTF MBTU

    ϝιου͕ېࢭ͞ΕΔʢજࡏతόάཁҼʣ  'PSNGJFMEͷ ҐஔҾ਺͕ېࢭʢ࣮ߦ࣌ΤϥʔΛ๷͙ͨΊʣ  .FUBJOEFYFTʹ͍ͭͯ΋ಉ༷ɺҐஔҾ਺͕ېࢭ  0SBDMFͷαϙʔτ͕ऴྃ εέδϡʔϧ