Escrevendo seu primeiro patch para Django

Introdução

Interessado em retribuir um pouco para a comunidade? Talvez você tenha encontrado uma falha no Django que você gostaria de ver corrigida, ou talvez exista uma pequena funcionalidade que poderia ser adicionada.

A melhor maneira de ver os seus próprios problemas serem resolvidos é contribuindo com o Django. Isto pode parecer assustador no começo, mas na verdade é bem simples. Nós lhe guiaremos por todo o processo para que você possa aprender através de exemplos.

Para quem se destina este tutorial?

Ver também

Se você está procurando por uma referência sobre como enviar pacotes, veja a Enviando patches documentação.

Para este tutorial, esperamos que você tenha ao menos um conhecimento básico de como o Django funciona. Isso significa que você precisa estar confortável com o tutoriai “Escrevendo sua primeira aplicação Django</intro/tutorial01>”. Além disso, você deve ter um bom conhecimento em Python. Mas se não tiver, “Dive Into Python” é um livro online fantástico (e gratuito) para iniciantes em Python.

Aqueles que não estão familiarizados com sistemas de controle de versão e Trac irão encontrar neste tutorial, e seus links inclusos, informações suficientes para iniciar. Entretanto, se você esta planejando contribuir com o Django regularmente, provavelmente você vai querer ler mais a respeito das destas diferentes ferramentas.

Para a maior parte, porém, este tutorial tenta explicar o máximo possível, tal que possa ser usado por um público mais amplo.

Onde obter ajuda:

Se você estiver enfrentando problemas durante este tutorial, por favor envie um mensagem par django-developers ou acesse `#django-dev on irc.freenode.net`__ para conversar com outros usuários de Django que podem ser capazes de ajudá-lo.

O que este tutorial cobre?

Nós iremos guir você para que contribua com um “patch” para o Django pela primeira vez. Ao final deste tutorial, você deve possuir o conhecimento básico tanto para as ferramentas quanto para os processos envolvidos. Nós iremos cobrir especialmente os seguintes tópicos:

  • Instalando Git.

  • Como baixar uma cópia de desenvolvimento do Django.

  • Executando suite de testes do Django.

  • Escrevendo um teste para o seu patch.

  • Escrevendo o código para o seu patch.

  • Testando o seu patch.

  • Gerando um arquivo de patch para as suas alterações.

  • Onde encontrar mais informações.

Uma vez que você terminar este tutorial, você pode olhar o restante da Documentação do Django sobre contribuição Ela contém muitas informações úteis e deve ser lida por quem deseja se tornar um contribuidor do Django. Caso tenha perguntas, a documentação provavelmente contem as respostas.

Python 3 é requerido!

Este tutorial acredita que você já está usando o Python 3. Obtenha a última versão do Python na sua página de download <https://www.python.org/download/>` ou com seu sistema de controle de pacotes.

Para usuários do Windows

Quando instalar o Python no Windows, tenha certeza que você marcou a opção “Add python.exe to Path”, então ele sempre vai estar disponível na linha de comando.

Código de Conduta

Como um contribuidor, você pode nos ajudar a manter a comunidade Django aberta e solidária. Por favor, leia e siga nosso ‘Código de conduta’ <https://www.djangoproject.com/conduct/>`_.

Instalando Git

Para este tutorial será necessário ter o Git instalado para fazer o download da versão corrente de desenvolvimento do Django e para gerar arquivos de “patch” para as mudanças que você faça.

Para verificar se o Git está instalado ou não no seu sistema, digite git na linha de comando. Se o resultado for uma mensagem dizendo que o comando não pode ser encontrado, você terá que fazer o download e instalar o Git. Dê uma olhada em `Git’s download page`__.

Para usuários do Windows

Ao instalar o Git no Windows, é recomendável que você escolha a opção “Git Bash” para que o Git seja executado em seu próprio shell. Este tutorial assume que foi assim que você instalou.

Caso não esteja familiar com o Git, você sempre pode achar mais sobre seus comandos (uma vez que estiver instalado) digitando “git help” na linha de comando.

Obtendo uma cópia da versão de desenvolvimento do Django

O primeiro Passo para contribuir com o Django é copiar o código fonte. Na linha de comando use o comando “cd” para navegar até o diretório que você deseja manter a sua cópia local do Django.

Baixe o código fonte do repositório do Django usando o seguinte comando:

$ git clone https://github.com/django/django.git

Agora que você tem uma cópia local do Django, você pode instalá-lo da mesma forma que você instalaria qualquer pacote usando pip. A maneira mais conveniente de fazer isso é usando um ambiente virtual (ou virtualenv) que é um recurso embutido no Python que permite que você mantenha um diretório separado para os pacotes instalados de cada um dos seus projetos para que uns não interfiram com os outros.

É uma boa idéia mater todos os seus virtualenvs em um lugar, por exemplo em .virtualenvs/ no seu diretório pessoal. Crie-o se ele ainda não existe:

$ mkdir ~/.virtualenvs

Agora crie uma nova virtualenv executando:

$ python3 -m venv ~/.virtualenvs/djangodev

O caminho é o local onde o novo ambiente será salvo em seu computador.

Para usuários do Windows

Usando o módulo embutido venv não irá funcionar se você também estiver usando o Git Bash shell no Windows, já que os scripts de ativação só são criados para o shell do sistema (.bat)e para o PowerShell (.ps1). Em vez disso use o pacote virtualenv:

$ pip install virtualenv
$ virtualenv ~/.virtualenvs/djangodev

Para usuários Ubuntu.

Em algumas versões do Ubuntu pode ser que o comando acima falhe. Ao invés disso use o pacote virtualenv, antes certifique-se que você tem o pip3:

$ sudo apt-get install python3-pip
$ # Prefix the next command with sudo if it gives a permission denied error
$ pip3 install virtualenv
$ virtualenv --python=`which python3` ~/.virtualenvs/djangodev

O passo final na criação da sua virtualenv é ativá-la:

$ source ~/.virtualenvs/djangodev/bin/activate

Se o comando source não estiver disponível, você pode tentar usar um ponto em vez disso:

$ . ~/.virtualenvs/djangodev/bin/activate

Para usuários do Windows

Para ativar seu virtualenv no Windows, rode:

$ source ~/virtualenvs/djangodev/Scripts/activate

Você tem que ativar o virtualenv sempre que abrir uma nova janela de terminal. virtualenvwrapper__ é uma ferramenta útil para deixar isso mais conveniente.

Tudo o que você instalar através do pip a partir de agora será instalado no seu novo virtualenv, isolado de outros ambientes e pacotes de todo o sistema. Além disso, o nome do virtualenv atualmente ativado é exibido na linha de comando para lhe ajudar a saber qual deles você está usando. Vá em frente e instale a cópia previamente clonada do Django:

$ pip install -e /path/to/your/local/clone/django/

A versão instalada do Django agora está apontando para a sua cópia local. Você verá as mudanças que fizer de forma imediata, o que é uma grande ajuda quando se está escrevendo o primeiro patch

Revertendo para uma revisão anterior do Django

Para este tutorial usaremos o ticket #24788 como caso de estudo, então iremos voltar no histórico de versão do Django no git para antes que o “patch” deste ticket tenha sido aplicado. Isso nos possibilitará ir através de todos os passos envolvidos em escrever um patch do início, incluindo rodar a suíte de testes do Django.

Tenha em mente que enquanto estamos usando uma revisão antiga da árvore do Django, para o propósito do tutorial abaixo, você deve sempre usar a versão corrente da árvore de desenvolvimento quando for trabalhar no seu próprio patch para um ticket!

Nota

O patch para este ticket foi escrito por Paweł Marczewskie foi aplicado ao Django como `commit 4df7e8483b2679fc1cba3410f08960bac6f51115`__. Consequentemente, usaremos a revisão anterior a esta, `commit 4ccfc4439a7add24f8db4ef3960d02ef8ae09887`__.

Navegue até o diretório raiz do Django (o diretório que contém ‘django’, ‘docs, ‘testes’, ‘autores’ e etc). Pode-se verificar então a revisão mais antiga do Django que utilizaremos no tutorial abaixo:

$ git checkout 4ccfc4439a7add24f8db4ef3960d02ef8ae09887

Executando a suíte de testes do Django pela primeira vez

Quando contribuir para o Django é muito imoprtante que suas mudanças no código não introduzam bugs em outras área do Django. Uma maneira de verificar se o Django ainda funciona depois de adicionar suas alterações é executando a suite de testes do Django. Se ainda passa em todos os testes, então você pode ter uma certeza razoável de que suas alterações não quebraram completamente o Django. Se você nunca rodou os testes antes, é uma boa idéia executá-los uma vez só para estar familiarizado com o que a sua saída deveria parecer.

Antes de rodar a suíte de testes, instale suas dependências primeiramente entrando no diretório tests/ do Django e então executando-a.

$ pip install -r requirements/py3.txt

Agora estamos prontos para executar a suite de testes. Se você estiver usando o GNU/Linux, Mac OS X ou alguma outra distro do UNIX, execute:

$ ./runtests.py

Agora sente e relaxe. A suíte de testes do Django tem mais de 9.600 testes diferentes, por isso pode levar de 5 a 15 minutos para ser executado, dependendo da velocidade do seu computador.

Enquanto a suíte de testes do Django estiver sendo executada, você verá um fluxo de caracteres representando o status de cada teste conforme vão sendo executados. “E” indica que foi lançado um erro durante o teste, “F” indica que as asserções do teste falharam. Ambos são considerados como se o teste tivesse falhado. Enquanto que “x” e “s” indicam falhas esperadas e testes ignorados, respectivamente. Pontos indicam que o teste passou.

Testes pulados são tipicamente devido à falta de bibliotecas externas necessárias para executar o teste; veja :ref:’running-unit-tests-dependencies’ para uma lista de dependencias e certifique-se de instalar qualquer um para testes relacionados com as mudanças que está fazendo. (Não necessitamos nenhum para este tutorial).

Once the tests complete, you should be greeted with a message informing you whether the test suite passed or failed. Since you haven’t yet made any changes to Django’s code, the entire test suite should pass. If you get failures or errors make sure you’ve followed all of the previous steps properly. See Rodando os testes unitários for more information. If you’re using Python 3.5+, there will be a couple failures related to deprecation warnings that you can ignore. These failures have since been fixed in Django.

Perceba que o trunk mais recente do Django pode nem sempre estar estável. Quando estiver desenvolvendo utilizando o trunk você pode checar `builds de integração contínua do Django`__ para determinar se as falhas são específicas à sua máquina ou se elas estão presentes também nas builds oficiais do Django. Se você clicar para visualizar uma build em particular, você pode visualizar a “Matriz de Configuração”, que mostra falhas discriminadas por versão de Python e backend de banco de dados.

Nota

Para este tutorial e para o ticket em que iremos trabalhar, testar usando o SQLite é suficiente. Entretanto, é possível (e às vezes necessário) usar um banco de dados diferente para testes.

Escrevendo alguns testes para o seu ticket

Na maioria dos casos, para um patch ser aceito no Django, ele tem que incluir testes. Para patches de correção de erros, significa escrever um teste de regressão para garantir que o erro nunca mais seja reintroduzido no Django mais tarde. Um teste de regressão deve ser escrito de tal forma que irá falhar enquanto o erro ainda existir e passar uma vez que o erro foi corrigido. Para patches contendo novos recursos, você precisa incluir testes que garantem que os novos recursos estão funcionando corretamente. Eles também devem falhar quando o novo recurso não estiver presente, e em seguida passar, uma vez que foi implementado.

Uma boa maneira de fazer isso é escrever seus novos testes primeiro, antes de realizar quaisquer modificações no código. Este estilo de desenvolvimento é chamado `desenvolvimento orientado a testes`__ e pode ser aplicado tanto a projetos inteiros quanto a patches isolados. Após escrever seus testes você os roda para garantir que eles realmente falham (uma vez que você ainda não consertou o bug ou adicionou a nova funcionalidade). Se seus novos testes não falharem, você terá de corrigí-los para que falhem. Afinal, um teste de regressão que passa independentemente de um bug estar presente não é muito útil na prevenção de que um bug ocorra novamente no futuro.

Agora para nosso exemplo mão-na-massa.

Escrevendo alguns testes para o ticket #24788

Ticket #24788 propõe uma pequena adição: a habilidade de especificar o atributo de classe prefix nas classes de Form, então que:

[…] forms which ship with apps could effectively namespace themselves such
that N overlapping form fields could be POSTed at once and resolved to the
correct form.

Para resolver este ticket, iremos adicionar um atributo prefix a classe BaseForm. Quando criar instancias desta classe, passando um prefixo para o método __init__() ainda será setado o prefixo na instancia criada. Mas não passando um prefix (ou passando None) será usado o prefixo da classe. Antes de fazermos estas mudanças, nós iremos escrever alguns testes para verificar que nossas modificações funcionam corretamente e continuem funcionando corretamente no futuro.

Navegue para o diretório tests/forms_tests/tests/ do Django e abra o arquivo test_forms.py. Adicione o seguinte código na linha 1674 logo antes da função test_forms_with_null_boolean function:

def test_class_prefix(self):
    # Prefix can be also specified at the class level.
    class Person(Form):
        first_name = CharField()
        prefix = 'foo'

    p = Person()
    self.assertEqual(p.prefix, 'foo')

    p = Person(prefix='bar')
    self.assertEqual(p.prefix, 'bar')

Este novo teste verifica que a configuração de um prefixo nível de classe funciona como esperado, e que passando um parâmetro `` prefix`` ao criar uma instância ainda funciona também.

Mas esse lance de testar parece difícil...

Caso você nunca tenha lidado com testes anteriormente, num primeiro momento eles podem parecer um pouco difíceis de escrever . Felizmente, testes são um assunto muito extenso em programação de computadores, então existe muita informção por aí:

  • Uma boa introdução em testes para Django pode ser encontrada na documentação Writing and running tests.

  • Megulhando no Python (um livro gratuito online para desenvolvedores iniciantes de Python) inclui uma ótima `introdução ao Teste de Unidade`__.

  • After reading those, if you want something a little meatier to sink your teeth into, there’s always the Python unittest documentation.

Executando o seu novo teste

Lembre que na verdade ainda não fizemos nenhuma modificação no BaseForm, então os testes irão falhar. Vamos rodar todos os testes no diretório forms_tests para ter ceteza que é isso que acontecerá. Na linha de comando, acesse o diretório tests/ do Django e execute:

$ ./runtests.py forms_tests

Se os testes foram executados corretamente, você deverá ver uma falha que corresponde ao método de teste que adicionamos. Se todos estes testes passaram, então você deverá ter certeza que você adicionou o novo teste mostrado acima no diretório e classe apropriados.

Escrevendo o código para o seu ticket

Em seguida nós iremos adicionar a funcionalidade descrita no ticket #24788 ao Django.

Escrevendo o código para o ticket #24788

Vá até o diretório django/django/forms/` e abra o arquivo  ``forms.py. Ache a classe BaseForm a linha 72 e adicione o atributo de classe prefix logo após o field_order attribute:

class BaseForm(object):
    # This is the main implementation of all the Form logic. Note that this
    # class is different than Form. See the comments by the Form class for
    # more information. Any improvements to the form API should be made to
    # *this* class, not to the Form class.
    field_order = None
    prefix = None

Verificando se o seu teste passa agora

Uma vez que tenha modificado o Django, precisamos ter ceteza que os testes que escreveu anteriormente passem, então você poder ver se o código que escrevemos anteriormente está funcionando corretamente. Para rodar os testes no diretório forms_tests , acesse (cd) o diretório tests/ do Django e execute:

$ ./runtests.py forms_tests

Opa, ainda bem que escrevemos esses testes! Você ainda deve ver uma falha com a seguinte exceção

AssertionError: None != 'foo'

Esquecemos de adicionar comando de condição no método __init__. Vá em frente e altere self.prefix = prefix que está agora na linha 87 do django/forms/forms.py, adicionando o comando de condição.

if prefix is not None:
    self.prefix = prefix

Re-execute os testes e tudo deve passar. Caso contrário, tenha certeza que modificou corretamente a classe BaseForm como mostrado acima e copiado o novo teste corretamente.

Executando a suíte de testes do Django pela segunda vez

Depois de verificar que seu patch e seu teste estão funcionando corretamente, é uma boa ídeia executar toda a suite de testes do Django apenas para verificar se sua mudança não introduziu algum erro em outras áreas do Django. Ao passar com sucesso em toda a suite de testes, não garante que seu código esteja livre de erros, ele ajuda identificar muitos erros e regressões que poderiam passar despercebidos.

Para executar toda a suíte de testes do Django, `` cd`` no diretório `` testes / `` do Django e execute:

$ ./runtests.py

Contanto que você não veja qualquer falha, você está pronto para seguir.

Escrevendo Documentação

Isos é uma funcionalidade nova, então deve ser documentada. Adicione a seguinte seção na linha 1068 (no final do arquivo) do django/docs/ref/forms/api.txt:

The prefix can also be specified on the form class::

    >>> class PersonForm(forms.Form):
    ...     ...
    ...     prefix = 'person'

.. versionadded:: 1.9

    The ability to specify ``prefix`` on the form class was added.

Já que essa nova funcionalidade estará em uma próxima revisão esta é também adicionada nas notas de release do Django 1.9, na linha 164 sob a seção “Forms” no arquivo docs/releases/1.9.txt:

* A form prefix can be specified inside a form class, not only when
  instantiating a form. See :ref:`form-prefix` for details.

Para mais informações sobre escrita de documentação, incluindo uma explicação sobre o que versionadded significa, verifique Escrevendo a documentação. Essa página também inclui uma explicação sobre como construir uma cópia local da documentação, de maneira que você possa pré-visualizar o HTML que será gerado.

Criando um patch para as suas alterações

Agora é hora de gerar um arquivo de “patch” que pode ser feito o upload no Trac ou aplicado em outra cópia do Django. Para dar uma olhada no conteúdo do seu “patch”, execute o seguinte comando:

$ git diff

Isso mostrará as diferenças entre sua cópia corrente do Django (com suas modificações) e a revisão que inicialmente você realizou check out , anteriormente no tutorial.

Uma vez que tenha terminado de olhar o conteúdo do seu “patch”, pressione a tecla q para retornar a linha de comando. Se o conteúdo do “patch” estiver ok, pode rodar o seguinte comando para alvar o arquivo de “patch” no seu diretório de trabalho corrente.

$ git diff > 24788.diff

Agora você deve ter um arquivo chamado 24788.diff no diretório raiz do Django. Este arquivo de patch contém todas as suas modificações e deve se assemelhar a isto:

diff --git a/django/forms/forms.py b/django/forms/forms.py
index 509709f..d1370de 100644
--- a/django/forms/forms.py
+++ b/django/forms/forms.py
@@ -75,6 +75,7 @@ class BaseForm(object):
     # information. Any improvements to the form API should be made to *this*
     # class, not to the Form class.
     field_order = None
+    prefix = None

     def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
                  initial=None, error_class=ErrorList, label_suffix=None,
@@ -83,7 +84,8 @@ class BaseForm(object):
         self.data = data or {}
         self.files = files or {}
         self.auto_id = auto_id
-        self.prefix = prefix
+        if prefix is not None:
+            self.prefix = prefix
         self.initial = initial or {}
         self.error_class = error_class
         # Translators: This is the default suffix added to form field labels
diff --git a/docs/ref/forms/api.txt b/docs/ref/forms/api.txt
index 3bc39cd..008170d 100644
--- a/docs/ref/forms/api.txt
+++ b/docs/ref/forms/api.txt
@@ -1065,3 +1065,13 @@ You can put several Django forms inside one ``<form>`` tag. To give each
     >>> print(father.as_ul())
     <li><label for="id_father-first_name">First name:</label> <input type="text" name="father-first_name" id="id_father-first_name" /></li>
     <li><label for="id_father-last_name">Last name:</label> <input type="text" name="father-last_name" id="id_father-last_name" /></li>
+
+The prefix can also be specified on the form class::
+
+    >>> class PersonForm(forms.Form):
+    ...     ...
+    ...     prefix = 'person'
+
+.. versionadded:: 1.9
+
+    The ability to specify ``prefix`` on the form class was added.
diff --git a/docs/releases/1.9.txt b/docs/releases/1.9.txt
index 5b58f79..f9bb9de 100644
--- a/docs/releases/1.9.txt
+++ b/docs/releases/1.9.txt
@@ -161,6 +161,9 @@ Forms
   :attr:`~django.forms.Form.field_order` attribute, the ``field_order``
   constructor argument , or the :meth:`~django.forms.Form.order_fields` method.

+* A form prefix can be specified inside a form class, not only when
+  instantiating a form. See :ref:`form-prefix` for details.
+
 Generic Views
 ^^^^^^^^^^^^^

diff --git a/tests/forms_tests/tests/test_forms.py b/tests/forms_tests/tests/test_forms.py
index 690f205..e07fae2 100644
--- a/tests/forms_tests/tests/test_forms.py
+++ b/tests/forms_tests/tests/test_forms.py
@@ -1671,6 +1671,18 @@ class FormsTestCase(SimpleTestCase):
         self.assertEqual(p.cleaned_data['last_name'], 'Lennon')
         self.assertEqual(p.cleaned_data['birthday'], datetime.date(1940, 10, 9))

+    def test_class_prefix(self):
+        # Prefix can be also specified at the class level.
+        class Person(Form):
+            first_name = CharField()
+            prefix = 'foo'
+
+        p = Person()
+        self.assertEqual(p.prefix, 'foo')
+
+        p = Person(prefix='bar')
+        self.assertEqual(p.prefix, 'bar')
+
     def test_forms_with_null_boolean(self):
         # NullBooleanField is a bit of a special case because its presentation (widget)
         # is different than its data. This is handled transparently, though.

Então, o que faço a seguir?

Parabéns, você gerou seu primeiro patch do Django! Agora que você adquiriu esta experiência você pode colocar essas habilidades em prática ajudando a melhorar a base de código do Django. Gerar patches e anexá-los a tíquetes do Trac é útil, no entanto, como estamos utilizando git - adotar um fluxo de trabalho orientado a git é mais recomendável.

Visto que nós nunca fizemos commit das nossas modificações locais, execute o seguinte para ter seu branch do git de volta a um bom ponto de partida:

$ git reset --hard HEAD
$ git checkout master

Mais informações para novos contribuidores

Antes que você mergulhe na escrita de patches para o Django há mais algumas informações sobre contribuição que você provavelmente deveria dar uma olhada:

  • Você deve certificar-se de ler a documentação do Django em reinvidicando tickets e enviando patches. Ela abrange etiqueta Trac, como reivindicar bilhetes para si mesmo, estilo de codificação prevista para patches, e muitos outros detalhes importantes.

  • Os contribuidores de primeira viagem devem ler o documentação sobre contribuidores de primeira viagem. Ele tem muitos bons conselhos para aqueles que são novos para ajudar com Django.

  • Depois disso, se você ainda estiver ansioso para obter mais informações sobre como contribuir, você sempre pode navegar pelo resto da documentação do Django em contribuição. Ele contém um monte de informações úteis e deve ser sua primeira fonte para responder quaisquer perguntas que você possa ter.

Encontrando o seu primeiro ticket

Uma vez que tenha dado uma olhada nessa documentação, você estará pronto para ir procurar um ticket para escrever um “patch”. Preste especial atenção aos tickets marcados com o critério “easy pickings”. Estes tickets são em geral de natureza simples e bons para novos contribuidores. Uma vez que estiver familiarizado com contribuir para o Django, você pode ir adiante escrevendo “patches” para tickets mais difíceis e complicados.

Se você já quer começar agora (e ninguém iria culpá-lo!), tente dar uma olhada na lista de `tickets fáceis que precisam de patches`__ e os `tickets fáceis que têm patches que precisam de melhorias`__. Se você estiver familiarizado com a escrita de testes, você também pode olhar para a lista de `tickets simples que precisam de testes`__. Apenas lembre-se de seguir as orientações sobre a reivindicação de bilhetes que foram mencionados no link para a documentação do Django em reivindicar tickets e submeter patches.

E agora?

Depois de um ticket ter um “patch”, é necessário que este seja revisado por um segundo olho. Depois de fazer o upload de um patch ou submeter um “pull request”, tenha certeza de atualizar o metadado do ticket para “has patch”, “doesn’t need tests”, etc, então outros podem econtrá-lo para revisar. Contribuir nem sempre quer dizer escrever um “patch” do início. Revisar “patches” existentes é também uma contribuição que ajuda muito. Veja Triangulando tickets para detalhes.

Back to Top