Desenvolvimento de Sistema CRM com Django: Design de Tabelas, Autenticação e Controle de Acesso

Design de Tabelas para Sistema CRM

Para o projeto de um sistema CRM, é essencial definir a estrutura do banco de dados. As tabelas principais incluem uma tabela de clientes, tabela de usuários, tabela de campus e tabela de departamentos. Adicionalmente, para o módulo de vendas, são necessárias tabelas de registro de acompanhamento, tabela de inscrição e tabela de pagamentos. Para o módulo de supervisão de turmas, inclui-se uma tabela de turmas, tabela de registros de aula e tabela de registros de aprendizado.

Autenticação: Registro e Login

Implementação de formulários de registro usando Django's ModelForm. Abaixo, um exemplo de formulário personalizado:

from django import forms
from . import models

class UserForm(forms.ModelForm):
    class Meta:
        model = models.User
        fields = "__all__"
        exclude = ['is_active']

    def clean_password(self):
        value = self.cleaned_data.get('password')
        # Adicionar validação personalizada aqui
        if len(value) < 8:
            raise forms.ValidationError('Senha muito curta')
        return value

    def clean(self):
        cleaned_data = super().clean()
        password = cleaned_data.get('password')
        confirm_password = cleaned_data.get('confirm_password')
        if password and confirm_password and password != confirm_password:
            self.add_error('confirm_password', 'Senhas não coincidem!')
        return cleaned_data

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        for field_name, field in self.fields.items():
            if not isinstance(field, forms.BooleanField):
                field.widget.attrs.update({'class': 'form-control'})

# View para registro
def register_view(request):
    form_instance = UserForm()
    if request.method == 'POST':
        form_instance = UserForm(request.POST)
        if form_instance.is_valid():
            form_instance.save()
            return redirect('home')
    return render(request, 'registration.html', {'form': form_instance})

# View para edição
def edit_user_view(request, user_id):
    user_obj = models.User.objects.get(pk=user_id)
    form_instance = UserForm(instance=user_obj)
    if request.method == 'POST':
        form_instance = UserForm(request.POST, instance=user_obj)
        if form_instance.is_valid():
            form_instance.save()
            return redirect('user_list')
    return render(request, 'edit_user.html', {'form': form_instance})

No template, o formulário pode ser renderizado com:

{% for field in form %}
    <label>{{ field.label }}</label>
    {{ field }}
    {% if field.errors %}
        <span class="error">{{ field.errors.0 }}</span>
    {% endif %}
{% endfor %}

Exibição de Dados

Ao exibir dados, considere diferentes tipos de campos:

  • Para campos comuns: acessar o atributo diretamente, como obj.field_name.
  • Para campos com choices: usar obj.get_field_name_display() para exibir o valor legível.
  • Para chaves estrangeiras: acessar o objeto relacionado via obj.foreign_key e seus campos.
  • Métodos personalizados podem ser definidos no modelo, por exemplo, para retornar strings HTML seguras com mark_safe.

Exclusão de Dados

Implementar exclusão simples usando object.delete() após confirmação.

Paginação

Calcular intervalos para paginação: para página page_num, o início é (page_num - 1) * per_page e o fim é page_num * per_page. Aplicar isso em queries como data[start:end].

Busca por Texto

Utilizar filtros com Q objects para buscas flexíveis. Exemplo:

from django.db.models import Q

def search_records(query, field_names):
    q_condition = Q()
    q_condition.connector = 'OR'
    for field in field_names:
        q_condition.children.append((f'{field}__icontains', query))
    return q_condition

# Uso: Model.objects.filter(search_records('termo', ['nome', 'email']))

Preservar Condições de Busca na Paginação

Manipular request.GET para manter parâmetros de busca. Exemplo:

query_params = request.GET.copy()
query_params['page'] = new_page_number
redirect_url = f"{request.path}?{query_params.urlencode()}"

Redirecionamento Após Edição

Incluir a URL atual como parâmetro next nos links de edição. Após salvar, redirecionar para esse parâmetro.

Tag de Template Personalizada

Criar uma tag simple_tag para gerar URLs com parâmetros:

from django import template
from django.urls import reverse

register = template.Library()

@register.simple_tag
def url_with_next(request, view_name, *args, **kwargs):
    next_path = request.get_full_path()
    url = reverse(view_name, args=args, kwargs=kwargs)
    return f"{url}?next={next_path}"

No template:

{% load custom_tags %}
{% url_with_next request 'edit_view' object_id %}

Transações e Bloqueio de Linha

Para operações concorrentes, usar transações com bloqueio. Exemplo:

from django.db import transaction

with transaction.atomic():
    # Adquirir bloqueio em linhas específicas
    records = Model.objects.filter(pk__in=ids, assigned_to__isnull=True).select_for_update()
    if records.count() == len(ids):
        records.update(assigned_to=request.user)
    else:
        raise ValueError("Conflito de concorrência")

Inicialização em Lote de Registros

Criar múltiplos objetos de uma vez com bulk_create:

record_list = [Model(field=value) for item in data]
Model.objects.bulk_create(record_list)

Controel de Acesso por Requisição

Implementar um middleware para verificar permissões em cada requisição:

import re
from django.http import HttpResponseForbidden

class PermissionMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        current_url = request.path_info
        # Verificar whitelist e login
        # ...
        permissions = request.session.get('user_permissions', [])
        for perm in permissions:
            if re.match(f'^{perm["url"]}$', current_url):
                return self.get_response(request)
        return HttpResponseForbidden('Acesso não autorizado')

Design de Menu Dinâmico

Para menus dinâmicos, armazenar no modelo Menu e Permission com campos como is_menu, icon, e weight. Na autenticação, carregar menus na sessão.

Exemplo de template inclusion tag para menus de primeiro nível:

@register.inclusion_tag('menu.html')
def render_menu(request):
    menu_items = request.session.get('menus', [])
    current_path = request.path_info
    for item in menu_items:
        item['active'] = re.match(f'^{item["url"]}$', current_path) is not None
    return {'menu_items': menu_items}

No template:

{% load menu_tags %}
{% render_menu request %}

Gerenciamento de Permissões Não-Menu

Para permissões não listadas como menus, vinculá-las a menus pai usando campos como parent_permission. Isso permite organizar hierarquias de acesso.

Tags: Django rbac Python SQL autenticacao

Publicado em 6-9 07:04 por Thomas