Ticket #16779: 16779.diff

File 16779.diff, 25.7 KB (added by Tim Graham, 11 years ago)
  • docs/index.txt

    diff --git a/docs/index.txt b/docs/index.txt
    index a6d9ed2..e6eb77c 100644
    a b Are you new to Django or to programming? This is the place to start!  
    4747  :doc:`Part 4 <intro/tutorial04>`
    4848
    4949* **Advanced Tutorials:**
    50   :doc:`How to write reusable apps <intro/reusable-apps>`
     50  :doc:`How to write reusable apps <intro/reusable-apps>` |
     51  :doc:`Writing your first patch for Django <intro/contributing>`
    5152
    5253The model layer
    5354===============
  • new file docs/intro/contributing.txt

    diff --git a/docs/intro/contributing.txt b/docs/intro/contributing.txt
    new file mode 100644
    index 0000000..c365a39
    - +  
     1===================================
     2Writing your first patch for Django
     3===================================
     4
     5Introduction
     6============
     7
     8Interested in giving back to the community a little? Maybe you've found a bug
     9in Django that you'd like to see fixed, or maybe there's a small feature you
     10want added.
     11
     12Contributing back to Django itself is the best way to see your own concerns
     13addressed. This may seem daunting at first, but it's really pretty simple.
     14We'll walk you through the entire process, so you can learn by example.
     15
     16Who's this tutorial for?
     17------------------------
     18
     19For this tutorial, we expect that you have at least a basic understanding of
     20how Django works. This means you should be comfortable going through the
     21existing tutorials on :doc:`writing your first Django app</intro/tutorial01>`.
     22In addition, you should have a good understanding of Python itself. But if you
     23don't, `Dive Into Python`__ is a fantastic (and free) online book for beginning
     24Python programmers.
     25
     26Those of you who are unfamiliar with version control systems and Trac will find
     27that this tutorial and its links include just enough information to get started.
     28However, you'll probably want to read some more about these different tools if
     29you plan on contributing to Django regularly.
     30
     31For the most part though, this tutorial tries to explain as much as possible,
     32so that it can be of use to the widest audience.
     33
     34.. admonition:: Where to get help:
     35
     36    If you're having trouble going through this tutorial, please post a message
     37    to `django-users`__ or drop by `#django on irc.freenode.net`__ to chat
     38    with other Django users who might be able to help.
     39
     40__ http://diveintopython.net/toc/index.html
     41__ http://groups.google.com/group/django-users
     42__ irc://irc.freenode.net/django
     43
     44What does this tutorial cover?
     45------------------------------
     46
     47We'll be walking you through contributing a patch to Django for the first time.
     48By the end of this tutorial, you should have a basic understanding of both the
     49tools and the processes involved. Specifically, we'll be covering the following:
     50
     51* Installing Git.
     52* How to download a development copy of Django.
     53* Running Django's test suite.
     54* Writing a test for your patch.
     55* Writing the code for your patch.
     56* Testing your patch.
     57* Generating a patch file for your changes.
     58* Where to look for more information.
     59
     60Once you're done with the tutorial, you can look through the rest of
     61:doc:`Django's documentation on contributing</internals/contributing/index>`.
     62It contains lots of great information and is a must read for anyone who'd like
     63to become a regular contributor to Django. If you've got questions, it's
     64probably got the answers.
     65
     66Installing Git
     67==============
     68
     69For this tutorial, you'll need Git installed to download the current
     70development version of Django and to generate patch files for the changes you
     71make.
     72
     73To check whether or not you have Git installed, enter ``git`` into the command
     74line. If you get messages saying that this command could be found, you'll have
     75to download and install it, see `Git's download page`__.
     76
     77.. note::
     78
     79    If you're not that familiar with Git, you can always find out more about
     80    its commands (once it's installed) by typing ``git help`` into the command
     81    line.
     82
     83__ http://git-scm.com/download
     84
     85Getting a copy of Django's development version
     86==============================================
     87
     88The first step to contributing to Django is to get a copy of the source code.
     89If you're using ``virtualenv``, it's better if you don't use ``pip`` to install
     90Django, since that can cause Django's setup.py script to be run. Follow the
     91instructions below to use Git to checkout a copy of Django manually.
     92
     93From the command line, use the ``cd`` command to navigate to the directory
     94where you'll want your local copy of Django to live.
     95
     96Download the Django source code repository using the following command::
     97
     98    git clone https://github.com/django/django.git
     99
     100Rolling back to a previous revision of Django
     101=============================================
     102
     103For this tutorial, we'll be using `ticket #15315`__ as a case study, so we'll
     104be using an older revision of Django from before that ticket's patch was
     105applied. This will allow us to go through all of the steps involved in writing
     106that patch from scratch, including running Django's test suite.
     107
     108**Keep in mind that while we'll be using an older revision of Django's trunk
     109for the purposes of the tutorial below, you should always use the current
     110development revision of Django when working on your own patch for a ticket!**
     111
     112.. note::
     113
     114    The patch for this ticket was generously written by SardarNL and Will Hardy,
     115    and it was applied to Django as `changeset 16659`__. Consequently, we'll be
     116    using the revision of Django just prior to that, revision 16658 (this is
     117    from when Django used Subversion rather than Git for source control).
     118
     119__ https://code.djangoproject.com/ticket/15315
     120__ https://code.djangoproject.com/changeset/16659
     121
     122Navigate into Django's root directory (that's the one that contains ``django``,
     123``docs``, ``tests``, ``AUTHORS``, etc.). You can then check out the older
     124revision of Django that we'll be using in the tutorial below::
     125
     126    git checkout 1f770c62da69866835bb8fc9355128b6b493a97e
     127
     128Running Django's test suite for the first time
     129==============================================
     130
     131When contributing to Django it's very important that your code changes don't
     132introduce bugs into other areas of Django.  One way to check that Django stills
     133works after you make your changes is by running Django's test suite. If all
     134the tests still pass, then you can be reasonably sure that your changes
     135haven't completely broken Django. If you've never run Django's test suite
     136before, it's a good idea to run it once beforehand just to get familiar with
     137what its output is supposed to look like.
     138
     139
     140Setting Django up to run the test suite
     141---------------------------------------
     142
     143Before we can actually run the test suite though, we need to make sure that
     144your new local copy of Django is on your ``PYTHONPATH``; otherwise, the test
     145suite won't run properly. We also need to make sure that there aren't any
     146**other** copies of Django installed somewhere else that are taking priority
     147over your newer copy (this happens more often than you might think). To check
     148for these problems, start up the Python interpreter and follow along with the
     149code below::
     150
     151    >>> import django
     152    >>> django
     153    <module 'django' from '/.../django/__init__.pyc'>
     154
     155If you get an ``ImportError: No module named django`` after entering the first
     156line, then you'll need to add your new copy of Django to your ``PYTHONPATH``.
     157
     158If you didn't get any errors, then look at the path found in the third line
     159(abbreviated above as ``/.../django/__init__.pyc``). If that isn't the
     160directory that you put Django into earlier in this tutorial, then there is
     161**another** copy of Django on your ``PYTHONPATH`` that is taking priority over
     162the newer copy.  You'll either have to remove this older copy from your
     163``PYTHONPATH``, or add your new copy to the beginning of your ``PYTHONPATH``
     164so that it takes priority.
     165
     166.. note::
     167
     168    If you're a savvy Djangonaut you might be thinking that using ``virtualenv``
     169    would work perfectly for this type of thing, and you'd be right. Using
     170    ``virtualenv`` with the ``--no-site-packages`` option isolates your local
     171    copy of Django from the rest of your system and avoids potential conflicts.
     172
     173    Make sure you don't use ``pip`` to install Django, since that causes
     174    Django's setup.py script to be run. If you do, you'll lose Django's root
     175    directory and end up with only the code installed. So you'll still need to
     176    use Git to check out a copy of Django, and then add it to your
     177    ``PYTHONPATH``.
     178
     179Running the full test suite
     180---------------------------
     181
     182Once Django is setup properly, we can actually run the test suite. Simply
     183``cd`` into the Django ``tests/`` directory and run:
     184
     185.. code-block:: bash
     186
     187    ./runtests.py --settings=test_sqlite
     188
     189If you get an ``ImportError: No module named django.contrib`` error, you still
     190need to add your current copy of Django to your ``PYTHONPATH``.
     191
     192Otherwise, sit back and relax. Django's entire test suite has over 4000
     193different tests, so it can take anywhere from 5 to 15 minutes to run,
     194depending on the speed of your computer.
     195
     196.. note::
     197
     198    While Django's test suite is running, you'll see a stream of characters
     199    representing the status of each test as it's run. ``E`` indicates that an
     200    error was raised during a test, and ``F`` indicates that a test's
     201    assertions failed. Both of these are considered to be test failures.
     202    Meanwhile, ``x`` and ``s`` indicated expected failures and skipped tests,
     203    respectively.  Dots indicate passing tests.
     204
     205Once the process is done, you should be greeted with a message informing you
     206whether the test suite passed or failed. Since you haven't yet made any changes
     207to Django's code, the entire test suite **should** pass, however, the latest
     208Django trunk may not always be stable. In this case you might run into a couple
     209of failures that were later fixed:
     210
     211* ``test_week_view_allow_future
     212  (regressiontests.generic_views.dates.WeekArchiveViewTests)``: This test
     213  happened to pass in 2011 when it was added because January 1, 2012 is a
     214  Sunday. It was fixed to pass regardless of the year in which it was run
     215  in `[b9910bdd]`__.
     216
     217* ``int() argument must be a string or a number, not 'list'`` and
     218  ``"ManagementForm data is missing or has been tampered with`` errors in the
     219  ``contrib.formtools`` tests: These tests sometimes failed if you had a buggy
     220  version of ``simplejson`` (2.0.9 in particular). This was fixed in
     221  `[358e5a80]`__.
     222
     223__ https://github.com/django/django/commit/b9910bdd/
     224__ https://github.com/django/django/commit/358e5a80/
     225
     226If you get failures or errors besides what's described above, make sure you've
     227followed all of the previous steps properly. See :ref:`running-unit-tests` for
     228more information.
     229
     230Writing a test for your ticket
     231==============================
     232
     233In most cases, for a patch to be accepted into Django it has to include tests.
     234For bug fix patches, this means writing a regression test to ensure that the
     235bug is never reintroduced into Django later on. A regression test should be
     236written in such a way that it will fail while the bug still exists and pass
     237once the bug has been fixed. For patches containing new features, you'll need
     238to include tests which ensure that the new features are working correctly.
     239They too should fail when the new feature is not present, and then pass once it
     240has been implemented.
     241
     242A good way to do this is to write your new tests first, before making any
     243changes to the code. This style of development is called
     244`test-driven development`__ and can be applied to both entire projects and
     245single patches. After writing your tests, you then run them to make sure that
     246they do indeed fail (since you haven't fixed that bug or added that feature
     247yet). If your new tests don't fail, you'll need to fix them so that they do.
     248After all, a regression test that passes regardless of whether a bug is present
     249is not very helpful at preventing that bug from reoccurring down the road.
     250
     251Now for our hands on example.
     252
     253__ http://en.wikipedia.org/wiki/Test-driven_development
     254
     255Writing a test for ticket #15315
     256--------------------------------
     257
     258`Ticket #15315`__ describes the following, small feature addition:
     259
     260    Since Django 1.2, the ``ModelForm`` class can override widgets by
     261    specifying a ``'widgets'`` attribute in ``Meta``, similar to ``'fields'``
     262    or ``'exclude'``. The ``modelform_factory`` function doesn't have any way
     263    to specify widgets directly, so the only option is to define a new
     264    ``ModelForm`` subclass with a ``'widget'`` attribute in ``Meta``, and then
     265    pass that class to ``modelform_factory`` using the ``'form'`` argument.
     266    This is more complex than it needs to be.
     267
     268    The fix is to add a new keyword argument ``widgets=None`` to
     269    ``modelform_factory``.
     270
     271In order to resolve this ticket, we'll modify the ``modelform_factory``
     272function to accept a ``widgets`` keyword argument. Before we make those changes
     273though, we're going to write a test to verify that our modification functions
     274correctly and continues to function correctly in the future.
     275
     276Navigate to Django's ``tests/regressiontests/model_forms_regress/`` folder and
     277open the ``tests.py`` file. Find the :class:`FormFieldCallbackTests` class on
     278line 264 and add the ``test_factory_with_widget_argument`` test to it as shown
     279below::
     280
     281    class FormFieldCallbackTests(TestCase):
     282
     283        def test_factory_with_widget_argument(self):
     284            """ Regression for #15315: modelform_factory should accept widgets
     285                argument
     286            """
     287            widget = forms.Textarea()
     288
     289            # Without the widget, the form field should not have the widget set to a textarea
     290            Form = modelform_factory(Person)
     291            self.assertNotEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
     292
     293            # With a widget, the form field should have the widget set to a textarea
     294            Form = modelform_factory(Person, widgets={'name':widget})
     295            self.assertEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
     296
     297        def test_baseform_with_widgets_in_meta(self):
     298            ...
     299
     300This test checks to see if the function ``modelform_factory`` accepts the new
     301widgets argument specifying what widgets to use for each field. It also makes
     302sure that those form fields are using the specified widgets. Now we have the
     303test for our patch written.
     304
     305.. admonition:: But this testing thing looks kinda hard...
     306
     307    If you've never had to deal with tests before, they can look a little hard
     308    to write at first glance. Fortunately, testing is a *very* big subject in
     309    computer programming, so there's lots of information out there:
     310
     311    * A good first look at writing tests for Django can be found in the
     312      documentation on :doc:`Testing Django applications</topics/testing/>`.
     313    * Dive Into Python (a free online book for beginning Python developers)
     314      includes a great `introduction to Unit Testing`__.
     315    * After reading those, if you want something a little meatier to sink
     316      your teeth into, there's always the `Python unittest documentation`__.
     317
     318__ https://code.djangoproject.com/ticket/15315
     319__ http://diveintopython.net/unit_testing/index.html
     320__ http://docs.python.org/library/unittest.html
     321
     322Running your new test
     323---------------------
     324
     325Remember that we haven't actually made any modifications to
     326``modelform_factory`` yet, so our test is going to fail.  Let's run all the
     327tests in the ``model_forms_regress`` folder to make sure that's really what
     328happens. From the command line, ``cd`` into the Django ``tests/`` directory
     329and run:
     330
     331.. code-block:: bash
     332
     333    ./runtests.py --settings=test_sqlite model_forms_regress
     334
     335If tests ran correctly, you should see that one of the tests failed with an
     336error. Verify that it was the new ``test_factory_with_widget_argument`` test we
     337added above, and then go on to the next step.
     338
     339If all of the tests passed, then you'll want to make sure that you added the
     340new test shown above to the appropriate folder and class. It's also possible
     341that you have a second copy of Django on your ``PYTHONPATH`` that is taking
     342priority over this copy, and therefore it may be that copy of Django whose
     343tests are being run. To check if this might be the problem, refer to the
     344section `Setting Django up to run the test suite`_.
     345
     346Writing the code for your ticket
     347================================
     348
     349Next we'll be adding the functionality described in `ticket #15315`__ to Django.
     350
     351Writing the code for ticket #15315
     352----------------------------------
     353
     354Navigate to the ``django/django/forms/`` folder and open the ``models.py``
     355file. Find the ``modelform_factory`` function on line 370 and modify **the
     356first part of it** so that it reads as follows:
     357
     358.. code-block:: python
     359
     360    def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
     361                          formfield_callback=None,  widgets=None):
     362        # Create the inner Meta class. FIXME: ideally, we should be able to
     363        # construct a ModelForm without creating and passing in a temporary
     364        # inner class.
     365
     366        # Build up a list of attributes that the Meta object will have.
     367        attrs = {'model': model}
     368        if fields is not None:
     369            attrs['fields'] = fields
     370        if exclude is not None:
     371            attrs['exclude'] = exclude
     372        if widgets is not None:
     373            attrs['widgets'] = widgets
     374
     375        # If parent form class already has an inner Meta, the Meta we're
     376        # creating needs to inherit from the parent's inner meta.
     377        ...
     378
     379Notice that we're changing one line of code to add a new ``widgets`` keyword
     380argument to the function's signature, and then we're adding two lines of code
     381a little below that which set ``attrs['widgets']`` to the value of our new
     382keyword argument if it was supplied.
     383
     384Verifying your test now passes
     385------------------------------
     386
     387Once you're done modifying ``modelform_factory``, we need to make sure that the
     388test we wrote earlier passes now, so we can see whether the code we wrote above
     389is working right. To run the tests in the ``model_forms_regress`` folder,
     390``cd`` into the Django ``tests/`` directory and run:
     391
     392.. code-block:: bash
     393
     394    ./runtests.py --settings=test_sqlite model_forms_regress
     395
     396If everything passes, then we can move on. If it doesn't, make sure you
     397correctly modified the ``modelform_factory`` function as shown above and
     398copied the ``test_factory_with_widget_argument`` test correctly.
     399
     400__ https://code.djangoproject.com/ticket/15315
     401
     402Running Django's test suite for the second time
     403===============================================
     404
     405Once you've verified that your patch and your test are working correctly, it's
     406a good idea to run the entire Django test suite just to verify that your change
     407hasn't introduced any bugs into other areas of Django. While successfully
     408passing the entire test suite doesn't guarantee your code is bug free, it does
     409help identify many bugs and regressions that might otherwise go unnoticed.
     410
     411To run the entire Django test suite, ``cd`` into the Django ``tests/``
     412directory and run:
     413
     414.. code-block:: bash
     415
     416    ./runtests.py --settings=test_sqlite
     417
     418As long as you don't see any failures, you're good to go, and you can move on
     419to generating a patch file that can be uploaded to Trac. If you do have any
     420failures, you'll need to examine them in order to determine whether or not the
     421modifications you made caused them.
     422
     423Generating a patch for your changes
     424===================================
     425
     426Now it's time to generate a patch file that can be uploaded to Trac or applied
     427to another copy of Django. To get a look at the content of your patch, run the
     428following command::
     429
     430    git diff
     431
     432This will display the differences between your current copy of Django (with
     433your changes) and the revision that you initially checked out earlier in the
     434tutorial.
     435
     436Once you're done looking at the patch, hit the ``q`` key to exit back to the
     437command line.  If the patch's content looked okay, you can run the following
     438command to save the patch file to your current working directory::
     439
     440    git diff > 15315.diff
     441
     442You should now have a file in the root Django directory called ``15315.diff``.
     443This patch file contains all your changes and should look this:
     444
     445.. code-block:: diff
     446
     447    Index: django/forms/models.py
     448    ===================================================================
     449    --- django/forms/models.py  (revision 16658)
     450    +++ django/forms/models.py  (working copy)
     451    @@ -368,7 +368,7 @@
     452         __metaclass__ = ModelFormMetaclass
     453
     454     def modelform_factory(model, form=ModelForm, fields=None, exclude=None,
     455    -                       formfield_callback=None):
     456    +                       formfield_callback=None, widgets=None):
     457         # Create the inner Meta class. FIXME: ideally, we should be able to
     458         # construct a ModelForm without creating and passing in a temporary
     459         # inner class.
     460    @@ -379,7 +379,9 @@
     461             attrs['fields'] = fields
     462         if exclude is not None:
     463             attrs['exclude'] = exclude
     464    -
     465    +    if widgets is not None:
     466    +        attrs['widgets'] = widgets
     467    +
     468         # If parent form class already has an inner Meta, the Meta we're
     469         # creating needs to inherit from the parent's inner meta.
     470         parent = (object,)
     471
     472    Index: tests/regressiontests/model_forms_regress/tests.py
     473    ===================================================================
     474    --- tests/regressiontests/model_forms_regress/tests.py      (revision 16658)
     475    +++ tests/regressiontests/model_forms_regress/tests.py      (working copy)
     476    @@ -263,6 +263,20 @@
     477
     478     class FormFieldCallbackTests(TestCase):
     479
     480    +    def test_factory_with_widget_argument(self):
     481    +        """ Regression for #15315: modelform_factory should accept widgets
     482    +            argument
     483    +        """
     484    +        widget = forms.Textarea()
     485    +
     486    +        # Without a widget should not set the widget to textarea
     487    +        Form = modelform_factory(Person)
     488    +        self.assertNotEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
     489    +
     490    +        # With a widget should not set the widget to textarea
     491    +        Form = modelform_factory(Person, widgets={'name':widget})
     492    +        self.assertEqual(Form.base_fields['name'].widget.__class__, forms.Textarea)
     493    +
     494         def test_baseform_with_widgets_in_meta(self):
     495             """Regression for #13095: Using base forms with widgets defined in Meta should not raise errors."""
     496             widget = forms.Textarea()
     497
     498So what do I do next?
     499=====================
     500
     501Congratulations, you've generated your very first Django patch! Now that you've
     502got that under your belt, you can put those skills to good use by helping to
     503improve Django's codebase. Generating patches and attaching them to Trac
     504tickets is useful, however, you can also :doc:`submit pull requests on Github
     505</internals/contributing/writing-code/working-with-git>`.
     506
     507More information for new contributors
     508-------------------------------------
     509
     510Before you get too into writing patches for Django, there's a little more
     511information on contributing that you should probably take a look at:
     512
     513* You should make sure to read Django's documentation on
     514  :doc:`claiming tickets and submitting patches
     515  </internals/contributing/writing-code/submitting-patches>`.
     516  It covers Trac etiquette, how to claim tickets for yourself, expected
     517  coding style for patches, and many other important details.
     518* First time contributors should also read Django's :doc:`documentation
     519  for first time contributors</internals/contributing/new-contributors/>`.
     520  It has lots of good advice for those of us who are new to helping out
     521  with Django.
     522* After those, if you're still hungry for more information about
     523  contributing, you can always browse through the rest of
     524  :doc:`Django's documentation on contributing</internals/contributing/index>`.
     525  It contains a ton of useful information and should be your first source
     526  for answering any questions you might have.
     527
     528Finding your first real ticket
     529------------------------------
     530
     531Once you've looked through some of that information, you'll be ready to go out
     532and find a ticket of your own to write a patch for. Pay special attention to
     533tickets with the "easy pickings" criterion. These tickets are often much
     534simpler in nature and are great for first time contributors.  Once you're
     535familiar with contributing to Django, you can move on to writing patches for
     536more difficult and complicated tickets.
     537
     538If you just want to get started already (and nobody would blame you!), try
     539taking a look at the list of `easy tickets that need patches`__ and the
     540`easy tickets that have patches which need improvement`__. If you're familiar
     541with writing tests, you can also look at the list of
     542`easy tickets that need tests`__. Just remember to follow the guidelines about
     543claiming tickets that were mentioned in the link to Django's documentation on
     544:doc:`claiming tickets and submitting patches
     545</internals/contributing/writing-code/submitting-patches>`.
     546
     547__ https://code.djangoproject.com/query?status=new&status=reopened&has_patch=0&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority
     548__ https://code.djangoproject.com/query?status=new&status=reopened&needs_better_patch=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority
     549__ https://code.djangoproject.com/query?status=new&status=reopened&needs_tests=1&easy=1&col=id&col=summary&col=status&col=owner&col=type&col=milestone&order=priority
  • docs/intro/index.txt

    diff --git a/docs/intro/index.txt b/docs/intro/index.txt
    index afb1825..bca2d77 100644
    a b place: read this material to quickly get up and running.  
    1515   tutorial04
    1616   reusable-apps
    1717   whatsnext
     18   contributing
    1819
    1920.. seealso::
    2021
Back to Top