Django with multiple ManyToManyField form

Question:

This is my first Django app to prove to myself (and my company) that we should adopt Django, but so far it has proven tricky.

I’m trying to create an app that shows all the employees of a company and for each, all the employee’s many skills organized in categories.
Here’s my model:

from django.db import models

class Category(models.Model):
    name = models.CharField(max_length=64)

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Skill(models.Model):
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    name = models.CharField(max_length=64)

    def __str__(self):              # __unicode__ on Python 2
        return self.name

class Employee(models.Model):
    login = models.CharField(max_length=16)
    fullname = models.CharField(max_length=64)
    skills = models.ManyToManyField(Skill, through='EmployeeSkill')

    def __str__(self):              # __unicode__ on Python 2
        return self.fullname

class EmployeeSkill(models.Model):
    employee = models.ForeignKey(Employee, on_delete=models.CASCADE)
    skill    = models.ForeignKey(Skill,    on_delete=models.CASCADE)
    LEVELS = (
        ('0', 'None'),
        ('1', 'Very Basic Knowledge (hours)'),
        ('2', 'Basic Knowledge (days)'),
        ('3', 'Good Understanding Knowledge (weeks)'),
        ('4', 'Excellent Knowledge (months)'),
        ('5', 'Expert Knowledge (years)'),
    )
    level = models.CharField(max_length=1, choices=LEVELS)
    desired_level = models.CharField(max_length=1, choices=LEVELS)

    def __str__(self):
        return "{} level of knowledge for {} is {} / desired is {}".format(self.employee.fullname, self.skill.name, self.level, self.desired_level)

I’m able to create an Employee, a Skill and an EmployeeSkill, and even show all the employee skills for a given employee but where I’m struggling is how I should go about creating a form that shows all possible EmployeeSkills for an employee and allow that employee to change the level (and desired_level) associated with each EmployeeSkill.

Please help!

Asked By: Jerome Provensal

||

Answers:

You can generate a select for the options on the ManyToManyFIeld and render each option on the select and add the values using AJAX.


<select name="material" id="id_material" required>
    <option value="0" disabled selected>Selecciona el material</option>
     {% for material in materiales %}
       <option value="{{material.id}}">{{material.descripcion}} ({{material.id_tipo_material.unidad}}) -  Total = {{material.cantidad}}</option>
     {% endfor %}
</select>

And the AJAX request:

function agrega_material(){

        var url = '{% url "paquetes:agrega_material_paquete" %}';

        var paquete = $('#id_cantidad').data('paquete');

        var material = $('#id_material').val();

        var cantidad = $('#id_cantidad').val();

        $.post(url,
        {
            paquete:paquete,
            material:material,
            cantidad:cantidad
        })
        .done(function( data ) {
            if(data['status'] == 'ko'){
                Materialize.toast('Cantidad es más grande que el material restante.', 4000)
            }else{
                $('#forma_material_paquete').html(data);
                $('select').material_select();
                Materialize.toast('El material ha sido agregado.', 4000)
            }
        });

        return false;
};

You are gonna submit the form using AJAX, passing the data to a view of django and adding the element or making a modification on your views, then you can pop a notification if the request status was ok. Example of view:

def agrega_material_paquete(request):
if request.method == 'POST':
    #Procesar datos
    id_paquete = int(request.POST.get('paquete')) //Here you receive the data of the AJAX request.
    paquete = get_object_or_404(Paquete, id=id_paquete )
    id_material = int(request.POST.get('material'))
    material = get_object_or_404(Material, id=id_material)
    cantidad_material = int(material.cantidad)
    cantidad = int(request.POST.get('cantidad'))

    if cantidad_material >= cantidad:
        Material_por_paquete.objects.create(paquete=paquete,material=material,cantidad=cantidad)
        #Mostrar vista de nuevo
        form = FormaMateriales()
        materiales = Material.objects.filter(activo=True)
        id_paquete = request.POST.get('paquete')
        materiales_paquete = Material_por_paquete.materiales_paquete(id_paquete)
        html = render_to_string('paquetes/select_materiales.html', {'form': form,'materiales':materiales,'materiales_paquete':materiales_paquete,'paquete':paquete})
        return HttpResponse(html)
    else:
        return JsonResponse({'status':'ko'})
else:
    return JsonResponse({'status':'ko'})
Answered By: Marco Mancha