| 1 | =================================== |
| 2 | Writing your first patch for Django |
| 3 | =================================== |
| 4 | |
| 5 | Introduction |
| 6 | ============ |
| 7 | |
| 8 | Interested in giving back to the community a little? Maybe you've found a bug |
| 9 | in Django that you'd like to see fixed, or maybe there's a small feature you |
| 10 | want added. |
| 11 | |
| 12 | Contributing back to Django itself is the best way to see your own concerns |
| 13 | addressed. This may seem daunting at first, but it's really pretty simple. |
| 14 | We'll walk you through the entire process, so you can learn by example. |
| 15 | |
| 16 | Who's this tutorial for? |
| 17 | ------------------------ |
| 18 | |
| 19 | For this tutorial, we expect that you have at least a basic understanding of |
| 20 | how Django works. This means you should be comfortable going through the |
| 21 | existing tutorials on :doc:`writing your first Django app</intro/tutorial01>`. |
| 22 | In addition, you should have a good understanding of Python itself. But if you |
| 23 | don't, `Dive Into Python`__ is a fantastic (and free) online book for beginning |
| 24 | Python programmers. |
| 25 | |
| 26 | Those of you who are unfamiliar with version control systems and Trac will find |
| 27 | that this tutorial and its links include just enough information to get started. |
| 28 | However, you'll probably want to read some more about these different tools if |
| 29 | you plan on contributing to Django regularly. |
| 30 | |
| 31 | For the most part though, this tutorial tries to explain as much as possible, |
| 32 | so 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-developers`__ or drop by `#django-dev 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-developers |
| 42 | __ irc://irc.freenode.net/django-dev |
| 43 | |
| 44 | What does this tutorial cover? |
| 45 | ------------------------------ |
| 46 | |
| 47 | We'll be walking you through contributing a patch to Django for the first time. |
| 48 | By the end of this tutorial, you should have a basic understanding of both the |
| 49 | tools 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 | |
| 60 | Once you're done with the tutorial, you can look through the rest of |
| 61 | :doc:`Django's documentation on contributing</internals/contributing/index>`. |
| 62 | It contains lots of great information and is a must read for anyone who'd like |
| 63 | to become a regular contributor to Django. If you've got questions, it's |
| 64 | probably got the answers. |
| 65 | |
| 66 | Installing Git |
| 67 | ============== |
| 68 | |
| 69 | For this tutorial, you'll need Git installed to download the current |
| 70 | development version of Django and to generate patch files for the changes you |
| 71 | make. |
| 72 | |
| 73 | To check whether or not you have Git installed, enter ``git`` into the command |
| 74 | line. If you get messages saying that this command could be found, you'll have |
| 75 | to download and install it, see `Git's download page`__. |
| 76 | |
| 77 | If you're not that familiar with Git, you can always find out more about its |
| 78 | commands (once it's installed) by typing ``git help`` into the command line. |
| 79 | |
| 80 | __ http://git-scm.com/download |
| 81 | |
| 82 | Getting a copy of Django's development version |
| 83 | ============================================== |
| 84 | |
| 85 | The first step to contributing to Django is to get a copy of the source code. |
| 86 | From the command line, use the ``cd`` command to navigate to the directory |
| 87 | where you'll want your local copy of Django to live. |
| 88 | |
| 89 | Download the Django source code repository using the following command:: |
| 90 | |
| 91 | git clone https://github.com/django/django.git |
| 92 | |
| 93 | .. note:: |
| 94 | |
| 95 | For advanced users who wish to use ``virtualenv``, you can use:: |
| 96 | |
| 97 | pip install -e git+https://github.com/django/django.git#egg=django |
| 98 | |
| 99 | to install a live, editable source checkout into a virtual environment. |
| 100 | |
| 101 | Rolling back to a previous revision of Django |
| 102 | ============================================= |
| 103 | |
| 104 | For this tutorial, we'll be using `ticket #17549`__ as a case study, so we'll |
| 105 | be using an older revision of Django from before that ticket's patch was |
| 106 | applied. This will allow us to go through all of the steps involved in writing |
| 107 | that patch from scratch, including running Django's test suite. |
| 108 | |
| 109 | **Keep in mind that while we'll be using an older revision of Django's trunk |
| 110 | for the purposes of the tutorial below, you should always use the current |
| 111 | development revision of Django when working on your own patch for a ticket!** |
| 112 | |
| 113 | .. note:: |
| 114 | |
| 115 | The patch for this ticket was written by Ulrich Petri, and it was applied |
| 116 | to Django as `commit ac2052ebc84c45709ab5f0f25e685bf656ce79bc`__. |
| 117 | Consequently, we'll be using the revision of Django just prior to that, |
| 118 | `commit 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac`__. |
| 119 | |
| 120 | __ https://code.djangoproject.com/ticket/17549 |
| 121 | __ https://github.com/django/django/commit/ac2052ebc84c45709ab5f0f25e685bf656ce79bc |
| 122 | __ https://github.com/django/django/commit/39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac |
| 123 | |
| 124 | Navigate into Django's root directory (that's the one that contains ``django``, |
| 125 | ``docs``, ``tests``, ``AUTHORS``, etc.). You can then check out the older |
| 126 | revision of Django that we'll be using in the tutorial below:: |
| 127 | |
| 128 | git checkout 39f5bc7fc3a4bb43ed8a1358b17fe0521a1a63ac |
| 129 | |
| 130 | Running Django's test suite for the first time |
| 131 | ============================================== |
| 132 | |
| 133 | When contributing to Django it's very important that your code changes don't |
| 134 | introduce bugs into other areas of Django. One way to check that Django stills |
| 135 | works after you make your changes is by running Django's test suite. If all |
| 136 | the tests still pass, then you can be reasonably sure that your changes |
| 137 | haven't completely broken Django. If you've never run Django's test suite |
| 138 | before, it's a good idea to run it once beforehand just to get familiar with |
| 139 | what its output is supposed to look like. |
| 140 | |
| 141 | Setting Django up to run the test suite |
| 142 | --------------------------------------- |
| 143 | |
| 144 | .. note:: |
| 145 | |
| 146 | If you're using ``virtualenv``, you can skip this section. Using |
| 147 | ``virtualenv`` with the ``--no-site-packages`` option isolates your |
| 148 | copy of Django from the rest of your system and avoids potential conflicts. |
| 149 | |
| 150 | Before we can actually run the test suite, we need to make sure that your new |
| 151 | local copy of Django is on your ``PYTHONPATH``; otherwise, the test suite won't |
| 152 | run properly. We also need to make sure that there aren't any **other** copies |
| 153 | of Django installed somewhere else that are taking priority over your new |
| 154 | checkout (this happens more often than you might think). To check for these |
| 155 | problems, start up the Python interpreter and follow the code below:: |
| 156 | |
| 157 | >>> import django |
| 158 | >>> django |
| 159 | <module 'django' from '/.../django/__init__.pyc'> |
| 160 | |
| 161 | If you get an ``ImportError: No module named django`` after entering the first |
| 162 | line, then you'll need to add your new copy of Django to your ``PYTHONPATH``. |
| 163 | |
| 164 | If you didn't get any errors, then look at the path found in the third line |
| 165 | (abbreviated above as ``/.../django/__init__.pyc``). If that isn't the |
| 166 | directory that you put Django into earlier in this tutorial, then there is |
| 167 | **another** copy of Django on your ``PYTHONPATH`` that is taking priority over |
| 168 | the newer copy. You'll either have to remove this older copy from your |
| 169 | ``PYTHONPATH``, or add your new copy to the beginning of your ``PYTHONPATH`` |
| 170 | so that it takes priority:: |
| 171 | |
| 172 | export PYTHONPATH=/path/to/django:$PYTHONPATH |
| 173 | |
| 174 | or on Windows:: |
| 175 | |
| 176 | set PYTHONPATH=C:\path\to\django;%PYTHONPATH% |
| 177 | |
| 178 | Running the full test suite |
| 179 | --------------------------- |
| 180 | |
| 181 | Once Django is setup properly, we can actually run the test suite. Simply |
| 182 | ``cd`` into the Django ``tests/`` directory and, if you're using GNU/Linux, |
| 183 | Mac OS X or some other flavor of Unix, run:: |
| 184 | |
| 185 | ./runtests.py --settings=test_sqlite |
| 186 | |
| 187 | If you're using Windows, you'll need to prefix ``python`` before |
| 188 | ``runtests.py``. We're going to use the Unix style throughout the rest of the |
| 189 | tutorial, so just make this simple change wherever we are running tests, for |
| 190 | example:: |
| 191 | |
| 192 | python runtests.py --settings=test_sqlite |
| 193 | |
| 194 | If you get an ``ImportError: No module named django.contrib`` error, you still |
| 195 | need to add your current copy of Django to your ``PYTHONPATH`` (see above). |
| 196 | |
| 197 | Otherwise, sit back and relax. Django's entire test suite has over 4800 |
| 198 | different tests, so it can take anywhere from 5 to 15 minutes to run, |
| 199 | depending on the speed of your computer. |
| 200 | |
| 201 | .. note:: |
| 202 | |
| 203 | While Django's test suite is running, you'll see a stream of characters |
| 204 | representing the status of each test as it's run. ``E`` indicates that an |
| 205 | error was raised during a test, and ``F`` indicates that a test's |
| 206 | assertions failed. Both of these are considered to be test failures. |
| 207 | Meanwhile, ``x`` and ``s`` indicated expected failures and skipped tests, |
| 208 | respectively. Dots indicate passing tests. |
| 209 | |
| 210 | Skipped tests are typically due to missing external libraries required to |
| 211 | run the test; see :ref:`running-unit-tests-dependencies` for a list of |
| 212 | dependencies and be sure to install any for tests related to the changes |
| 213 | you are making (we won't need any for this tutorial). |
| 214 | |
| 215 | Once the tests complete, you should be greeted with a message informing you |
| 216 | whether the test suite passed or failed. Since you haven't yet made any changes |
| 217 | to Django's code, the entire test suite **should** pass. If you get failures or |
| 218 | errors make sure you've followed all of the previous steps properly. See |
| 219 | :ref:`running-unit-tests` for more information. |
| 220 | |
| 221 | Note that the latest Django trunk may not always be stable. When developing |
| 222 | against trunk, you can check `Django's continuous integration builds`__ to |
| 223 | determine if the failures are specific to your machine or if they are also |
| 224 | present in Django's official builds. |
| 225 | |
| 226 | __ http://ci.djangoproject.com/ |
| 227 | |
| 228 | .. note:: |
| 229 | |
| 230 | For this tutorial and the ticket we're working on, testing against SQLite |
| 231 | is sufficient, however, it's possible (and sometimes necessary) to |
| 232 | :ref:`run the tests using a different database |
| 233 | <running-unit-tests-settings>`. |
| 234 | |
| 235 | Writing some tests for your ticket |
| 236 | ================================== |
| 237 | |
| 238 | In most cases, for a patch to be accepted into Django it has to include tests. |
| 239 | For bug fix patches, this means writing a regression test to ensure that the |
| 240 | bug is never reintroduced into Django later on. A regression test should be |
| 241 | written in such a way that it will fail while the bug still exists and pass |
| 242 | once the bug has been fixed. For patches containing new features, you'll need |
| 243 | to include tests which ensure that the new features are working correctly. |
| 244 | They too should fail when the new feature is not present, and then pass once it |
| 245 | has been implemented. |
| 246 | |
| 247 | A good way to do this is to write your new tests first, before making any |
| 248 | changes to the code. This style of development is called |
| 249 | `test-driven development`__ and can be applied to both entire projects and |
| 250 | single patches. After writing your tests, you then run them to make sure that |
| 251 | they do indeed fail (since you haven't fixed that bug or added that feature |
| 252 | yet). If your new tests don't fail, you'll need to fix them so that they do. |
| 253 | After all, a regression test that passes regardless of whether a bug is present |
| 254 | is not very helpful at preventing that bug from reoccurring down the road. |
| 255 | |
| 256 | Now for our hands on example. |
| 257 | |
| 258 | __ http://en.wikipedia.org/wiki/Test-driven_development |
| 259 | |
| 260 | Writing some tests for ticket #17549 |
| 261 | ------------------------------------ |
| 262 | |
| 263 | `Ticket #17549`__ describes the following, small feature addition: |
| 264 | |
| 265 | It's useful for URLField to give you a way to open the URL; otherwise you |
| 266 | might as well use a CharField. |
| 267 | |
| 268 | In order to resolve this ticket, we'll add a ``render`` method to the |
| 269 | ``AdminURLFieldWidget`` in order to display a clickable link above the input |
| 270 | widget. Before we make those changes though, we're going to write a couple |
| 271 | tests to verify that our modification functions correctly and continues to |
| 272 | function correctly in the future. |
| 273 | |
| 274 | Navigate to Django's ``tests/regressiontests/admin_widgets/`` folder and |
| 275 | open the ``tests.py`` file. Add the following code on line 269 right before the |
| 276 | ``AdminFileWidgetTest`` class:: |
| 277 | |
| 278 | class AdminURLWidgetTest(DjangoTestCase): |
| 279 | def test_render(self): |
| 280 | w = widgets.AdminURLFieldWidget() |
| 281 | self.assertHTMLEqual( |
| 282 | conditional_escape(w.render('test', '')), |
| 283 | '<input class="vURLField" name="test" type="text" />' |
| 284 | ) |
| 285 | self.assertHTMLEqual( |
| 286 | conditional_escape(w.render('test', 'http://example.com')), |
| 287 | '<p class="url">Currently:<a href="http://example.com">http://example.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com" /></p>' |
| 288 | ) |
| 289 | |
| 290 | def test_render_idn(self): |
| 291 | w = widgets.AdminURLFieldWidget() |
| 292 | self.assertHTMLEqual( |
| 293 | conditional_escape(w.render('test', 'http://example-äüö.com')), |
| 294 | '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com" /></p>' |
| 295 | ) |
| 296 | |
| 297 | def test_render_quoting(self): |
| 298 | w = widgets.AdminURLFieldWidget() |
| 299 | self.assertHTMLEqual( |
| 300 | conditional_escape(w.render('test', 'http://example.com/<sometag>some text</sometag>')), |
| 301 | '<p class="url">Currently:<a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com/<sometag>some text</sometag>" /></p>' |
| 302 | ) |
| 303 | self.assertHTMLEqual( |
| 304 | conditional_escape(w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')), |
| 305 | '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com/<sometag>some text</sometag>" /></p>' |
| 306 | ) |
| 307 | |
| 308 | The new tests check to see that the ``render`` method we'll be adding works |
| 309 | correctly in a couple different situations. |
| 310 | |
| 311 | .. admonition:: But this testing thing looks kinda hard... |
| 312 | |
| 313 | If you've never had to deal with tests before, they can look a little hard |
| 314 | to write at first glance. Fortunately, testing is a *very* big subject in |
| 315 | computer programming, so there's lots of information out there: |
| 316 | |
| 317 | * A good first look at writing tests for Django can be found in the |
| 318 | documentation on :doc:`Testing Django applications</topics/testing/>`. |
| 319 | * Dive Into Python (a free online book for beginning Python developers) |
| 320 | includes a great `introduction to Unit Testing`__. |
| 321 | * After reading those, if you want something a little meatier to sink |
| 322 | your teeth into, there's always the `Python unittest documentation`__. |
| 323 | |
| 324 | __ https://code.djangoproject.com/ticket/17549 |
| 325 | __ http://diveintopython.net/unit_testing/index.html |
| 326 | __ http://docs.python.org/library/unittest.html |
| 327 | |
| 328 | Running your new test |
| 329 | --------------------- |
| 330 | |
| 331 | Remember that we haven't actually made any modifications to |
| 332 | ``AdminURLFieldWidget`` yet, so our tests are going to fail. Let's run all the |
| 333 | tests in the ``model_forms_regress`` folder to make sure that's really what |
| 334 | happens. From the command line, ``cd`` into the Django ``tests/`` directory |
| 335 | and run:: |
| 336 | |
| 337 | ./runtests.py --settings=test_sqlite admin_widgets |
| 338 | |
| 339 | If the tests ran correctly, you should see three failures corresponding to each |
| 340 | of the test methods we added. If all of the tests passed, then you'll want to |
| 341 | make sure that you added the new test shown above to the appropriate folder and |
| 342 | class. It's also possible that you have a second copy of Django on your |
| 343 | ``PYTHONPATH`` that is taking priority over this copy, and therefore it may be |
| 344 | that copy of Django whose tests are being run. To check if this might be the |
| 345 | problem, refer to the section `Setting Django up to run the test suite`_. |
| 346 | |
| 347 | Writing the code for your ticket |
| 348 | ================================ |
| 349 | |
| 350 | Next we'll be adding the functionality described in `ticket #17549`__ to Django. |
| 351 | |
| 352 | Writing the code for ticket #17549 |
| 353 | ---------------------------------- |
| 354 | |
| 355 | Navigate to the ``django/django/contrib/admin/`` folder and open the |
| 356 | ``widgets.py`` file. Find the ``AdminURLFieldWidget`` class on line 302 and add |
| 357 | the following ``render`` method after the existing ``__init__`` method:: |
| 358 | |
| 359 | def render(self, name, value, attrs=None): |
| 360 | html = super(AdminURLFieldWidget, self).render(name, value, attrs) |
| 361 | if value: |
| 362 | value = force_text(self._format_value(value)) |
| 363 | final_attrs = {'href': mark_safe(smart_urlquote(value))} |
| 364 | html = format_html( |
| 365 | '<p class="url">{0} <a {1}>{2}</a><br />{3} {4}</p>', |
| 366 | _('Currently:'), flatatt(final_attrs), value, |
| 367 | _('Change:'), html |
| 368 | ) |
| 369 | return html |
| 370 | |
| 371 | Verifying your test now passes |
| 372 | ------------------------------ |
| 373 | |
| 374 | Once you're done modifying Django, we need to make sure that the tests we wrote |
| 375 | earlier pass, so we can see whether the code we wrote above is working |
| 376 | correctly. To run the tests in the ``admin_widgets`` folder, ``cd`` into the |
| 377 | Django ``tests/`` directory and run:: |
| 378 | |
| 379 | ./runtests.py --settings=test_sqlite admin_widgets |
| 380 | |
| 381 | Oops, good thing we wrote those tests! You should still see 3 failures with |
| 382 | the following exception:: |
| 383 | |
| 384 | NameError: global name 'smart_urlquote' is not defined |
| 385 | |
| 386 | We forgot to add the import for that method. Go ahead and add the |
| 387 | ``smart_urlquote`` import at the end of line 13 of |
| 388 | ``django/contrib/admin/widgets.py`` so it looks as follows:: |
| 389 | |
| 390 | from django.utils.html import escape, format_html, format_html_join, smart_urlquote |
| 391 | |
| 392 | Re-run the tests and everything should pass. If it doesn't, make sure you |
| 393 | correctly modified the ``AdminURLFieldWidget`` class as shown above and |
| 394 | copied the new tests correctly. |
| 395 | |
| 396 | __ https://code.djangoproject.com/ticket/17549 |
| 397 | |
| 398 | Running Django's test suite for the second time |
| 399 | =============================================== |
| 400 | |
| 401 | Once you've verified that your patch and your test are working correctly, it's |
| 402 | a good idea to run the entire Django test suite just to verify that your change |
| 403 | hasn't introduced any bugs into other areas of Django. While successfully |
| 404 | passing the entire test suite doesn't guarantee your code is bug free, it does |
| 405 | help identify many bugs and regressions that might otherwise go unnoticed. |
| 406 | |
| 407 | To run the entire Django test suite, ``cd`` into the Django ``tests/`` |
| 408 | directory and run:: |
| 409 | |
| 410 | ./runtests.py --settings=test_sqlite |
| 411 | |
| 412 | As long as you don't see any failures, you're good to go. Note that this fix |
| 413 | also made a `small CSS change`__ to format the new widget. You can make the |
| 414 | change if you'd like, but we'll skip it for now in the interest of brevity. |
| 415 | |
| 416 | __ https://github.com/django/django/commit/ac2052ebc84c45709ab5f0f25e685bf656ce79bc#diff-0 |
| 417 | |
| 418 | Writing Documentation |
| 419 | ===================== |
| 420 | |
| 421 | This is a new feature, so it should be documented. Add the following on line |
| 422 | 925 of ``django/docs/ref/models/fields.txt`` beneath the existing docs for |
| 423 | ``URLField``:: |
| 424 | |
| 425 | .. versionadded:: 1.5 |
| 426 | |
| 427 | The current value of the field will be displayed as a clickable link above the |
| 428 | input widget. |
| 429 | |
| 430 | For more information on writing documentation, including an explanation of what |
| 431 | the ``versionadded`` bit is all about, see |
| 432 | :doc:`/internals/contributing/writing-documentation`. That page also includes |
| 433 | an explanation of how to build a copy of the documentation locally, so you can |
| 434 | preview the HTML that will be generated. |
| 435 | |
| 436 | Generating a patch for your changes |
| 437 | =================================== |
| 438 | |
| 439 | Now it's time to generate a patch file that can be uploaded to Trac or applied |
| 440 | to another copy of Django. To get a look at the content of your patch, run the |
| 441 | following command:: |
| 442 | |
| 443 | git diff |
| 444 | |
| 445 | This will display the differences between your current copy of Django (with |
| 446 | your changes) and the revision that you initially checked out earlier in the |
| 447 | tutorial. |
| 448 | |
| 449 | Once you're done looking at the patch, hit the ``q`` key to exit back to the |
| 450 | command line. If the patch's content looked okay, you can run the following |
| 451 | command to save the patch file to your current working directory:: |
| 452 | |
| 453 | git diff > 17549.diff |
| 454 | |
| 455 | You should now have a file in the root Django directory called ``17549.diff``. |
| 456 | This patch file contains all your changes and should look this: |
| 457 | |
| 458 | .. code-block:: diff |
| 459 | |
| 460 | diff --git a/django/contrib/admin/widgets.py b/django/contrib/admin/widgets.py |
| 461 | index 1e0bc2d..9e43a10 100644 |
| 462 | --- a/django/contrib/admin/widgets.py |
| 463 | +++ b/django/contrib/admin/widgets.py |
| 464 | @@ -10,7 +10,7 @@ from django.contrib.admin.templatetags.admin_static import static |
| 465 | from django.core.urlresolvers import reverse |
| 466 | from django.forms.widgets import RadioFieldRenderer |
| 467 | from django.forms.util import flatatt |
| 468 | -from django.utils.html import escape, format_html, format_html_join |
| 469 | +from django.utils.html import escape, format_html, format_html_join, smart_urlquote |
| 470 | from django.utils.text import Truncator |
| 471 | from django.utils.translation import ugettext as _ |
| 472 | from django.utils.safestring import mark_safe |
| 473 | @@ -306,6 +306,18 @@ class AdminURLFieldWidget(forms.TextInput): |
| 474 | final_attrs.update(attrs) |
| 475 | super(AdminURLFieldWidget, self).__init__(attrs=final_attrs) |
| 476 | |
| 477 | + def render(self, name, value, attrs=None): |
| 478 | + html = super(AdminURLFieldWidget, self).render(name, value, attrs) |
| 479 | + if value: |
| 480 | + value = force_text(self._format_value(value)) |
| 481 | + final_attrs = {'href': mark_safe(smart_urlquote(value))} |
| 482 | + html = format_html( |
| 483 | + '<p class="url">{0} <a {1}>{2}</a><br />{3} {4}</p>', |
| 484 | + _('Currently:'), flatatt(final_attrs), value, |
| 485 | + _('Change:'), html |
| 486 | + ) |
| 487 | + return html |
| 488 | + |
| 489 | class AdminIntegerFieldWidget(forms.TextInput): |
| 490 | class_name = 'vIntegerField' |
| 491 | |
| 492 | diff --git a/docs/ref/models/fields.txt b/docs/ref/models/fields.txt |
| 493 | index 809d56e..d44f85f 100644 |
| 494 | --- a/docs/ref/models/fields.txt |
| 495 | +++ b/docs/ref/models/fields.txt |
| 496 | @@ -922,6 +922,10 @@ Like all :class:`CharField` subclasses, :class:`URLField` takes the optional |
| 497 | :attr:`~CharField.max_length`argument. If you don't specify |
| 498 | :attr:`~CharField.max_length`, a default of 200 is used. |
| 499 | |
| 500 | +.. versionadded:: 1.5 |
| 501 | + |
| 502 | +The current value of the field will be displayed as a clickable link above the |
| 503 | +input widget. |
| 504 | |
| 505 | Relationship fields |
| 506 | =================== |
| 507 | diff --git a/tests/regressiontests/admin_widgets/tests.py b/tests/regressiontests/admin_widgets/tests.py |
| 508 | index 4b11543..94acc6d 100644 |
| 509 | --- a/tests/regressiontests/admin_widgets/tests.py |
| 510 | +++ b/tests/regressiontests/admin_widgets/tests.py |
| 511 | @@ -265,6 +265,35 @@ class AdminSplitDateTimeWidgetTest(DjangoTestCase): |
| 512 | '<p class="datetime">Datum: <input value="01.12.2007" type="text" class="vDateField" name="test_0" size="10" /><br />Zeit: <input value="09:30:00" type="text" class="vTimeField" name="test_1" size="8" /></p>', |
| 513 | ) |
| 514 | |
| 515 | +class AdminURLWidgetTest(DjangoTestCase): |
| 516 | + def test_render(self): |
| 517 | + w = widgets.AdminURLFieldWidget() |
| 518 | + self.assertHTMLEqual( |
| 519 | + conditional_escape(w.render('test', '')), |
| 520 | + '<input class="vURLField" name="test" type="text" />' |
| 521 | + ) |
| 522 | + self.assertHTMLEqual( |
| 523 | + conditional_escape(w.render('test', 'http://example.com')), |
| 524 | + '<p class="url">Currently:<a href="http://example.com">http://example.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com" /></p>' |
| 525 | + ) |
| 526 | + |
| 527 | + def test_render_idn(self): |
| 528 | + w = widgets.AdminURLFieldWidget() |
| 529 | + self.assertHTMLEqual( |
| 530 | + conditional_escape(w.render('test', 'http://example-äüö.com')), |
| 531 | + '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com">http://example-äüö.com</a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com" /></p>' |
| 532 | + ) |
| 533 | + |
| 534 | + def test_render_quoting(self): |
| 535 | + w = widgets.AdminURLFieldWidget() |
| 536 | + self.assertHTMLEqual( |
| 537 | + conditional_escape(w.render('test', 'http://example.com/<sometag>some text</sometag>')), |
| 538 | + '<p class="url">Currently:<a href="http://example.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example.com/<sometag>some text</sometag>" /></p>' |
| 539 | + ) |
| 540 | + self.assertHTMLEqual( |
| 541 | + conditional_escape(w.render('test', 'http://example-äüö.com/<sometag>some text</sometag>')), |
| 542 | + '<p class="url">Currently:<a href="http://xn--example--7za4pnc.com/%3Csometag%3Esome%20text%3C/sometag%3E">http://example-äüö.com/<sometag>some text</sometag></a><br />Change:<input class="vURLField" name="test" type="text" value="http://example-äüö.com/<sometag>some text</sometag>" /></p>' |
| 543 | + ) |
| 544 | |
| 545 | class AdminFileWidgetTest(DjangoTestCase): |
| 546 | def test_render(self): |
| 547 | |
| 548 | So what do I do next? |
| 549 | ===================== |
| 550 | |
| 551 | Congratulations, you've generated your very first Django patch! Now that you've |
| 552 | got that under your belt, you can put those skills to good use by helping to |
| 553 | improve Django's codebase. Generating patches and attaching them to Trac |
| 554 | tickets is useful, however, you can also :doc:`submit pull requests on Github |
| 555 | </internals/contributing/writing-code/working-with-git>`. |
| 556 | |
| 557 | More information for new contributors |
| 558 | ------------------------------------- |
| 559 | |
| 560 | Before you get too into writing patches for Django, there's a little more |
| 561 | information on contributing that you should probably take a look at: |
| 562 | |
| 563 | * You should make sure to read Django's documentation on |
| 564 | :doc:`claiming tickets and submitting patches |
| 565 | </internals/contributing/writing-code/submitting-patches>`. |
| 566 | It covers Trac etiquette, how to claim tickets for yourself, expected |
| 567 | coding style for patches, and many other important details. |
| 568 | * First time contributors should also read Django's :doc:`documentation |
| 569 | for first time contributors</internals/contributing/new-contributors/>`. |
| 570 | It has lots of good advice for those of us who are new to helping out |
| 571 | with Django. |
| 572 | * After those, if you're still hungry for more information about |
| 573 | contributing, you can always browse through the rest of |
| 574 | :doc:`Django's documentation on contributing</internals/contributing/index>`. |
| 575 | It contains a ton of useful information and should be your first source |
| 576 | for answering any questions you might have. |
| 577 | |
| 578 | Finding your first real ticket |
| 579 | ------------------------------ |
| 580 | |
| 581 | Once you've looked through some of that information, you'll be ready to go out |
| 582 | and find a ticket of your own to write a patch for. Pay special attention to |
| 583 | tickets with the "easy pickings" criterion. These tickets are often much |
| 584 | simpler in nature and are great for first time contributors. Once you're |
| 585 | familiar with contributing to Django, you can move on to writing patches for |
| 586 | more difficult and complicated tickets. |
| 587 | |
| 588 | If you just want to get started already (and nobody would blame you!), try |
| 589 | taking a look at the list of `easy tickets that need patches`__ and the |
| 590 | `easy tickets that have patches which need improvement`__. If you're familiar |
| 591 | with writing tests, you can also look at the list of |
| 592 | `easy tickets that need tests`__. Just remember to follow the guidelines about |
| 593 | claiming tickets that were mentioned in the link to Django's documentation on |
| 594 | :doc:`claiming tickets and submitting patches |
| 595 | </internals/contributing/writing-code/submitting-patches>`. |
| 596 | |
| 597 | __ 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 |
| 598 | __ 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 |
| 599 | __ 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 |
| 600 | |
| 601 | What's next? |
| 602 | ------------ |
| 603 | |
| 604 | After a ticket has a patch, it needs to be reviewed by a second set of eyes. |
| 605 | After uploading a patch or submitting a pull request, be sure to update the |
| 606 | ticket metadata by setting the flags on the ticket to say "has patch", |
| 607 | "doesn't need tests", etc, so others can find it for review. Contributing |
| 608 | doesn't necessarily always mean writing a patch from scratch. Reviewing |
| 609 | existing patches is also a very helpful contribution. See |
| 610 | :doc:`/internals/contributing/triaging-tickets` for details. |