Saving newly-created records overwrites existing ones

This code succeeds in creating and saving an Event object and its related Participant objects, but the newly-created Participant objects overwrite ones already in the db:

Models:

# A membership 'roster' (i.e. a group of potential players)
class Roster(models.Model):
	name = models.CharField(max_length=50, null=False, blank=False, help_text="Roster name")
	slug = models.CharField(max_length=50, null=False, blank=False, help_text="Used in URLs")
	location = models.CharField(max_length=50, null=False, blank=False, default="", help_text="The default location") 
	comment = models.CharField(max_length=400, null=True, blank=True, help_text="A comment about the group")

# A specific event with its participants
class Event(models.Model):
	date_time = models.CharField(max_length=50, null=False, blank=False) 
	location = models.CharField(max_length=50, null=False, blank=False) 
	roster = models.ForeignKey(Roster, related_name='events', on_delete=models.CASCADE, default=None, null=True, help_text="The event's roster") 
	comment = models.CharField(max_length=400, null=True, blank=True)

# A participant in a specific event. 
class Participant(models.Model):
	member = models.OneToOneField(Member, on_delete=models.CASCADE,primary_key=True,) #null=False?
	event = models.ForeignKey(Event, on_delete=models.CASCADE, null=False, related_name="participants")
	status = models.CharField(max_length=50, choices=StatusChoices, default="Unknown")
	status = models.CharField(max_length=50, choices=StatusChoices, default="Unknown")
	phone = models.CharField(max_length=15, default=None, blank=True, null=True)
	comment = models.CharField(max_length=400, blank=True, null=True)

Form:

class EventForm(forms.ModelForm):
	location = forms.CharField(required=True, widget=forms.TextInput(attrs={'class': 'border-input'}))
	date_time = forms.CharField(required=True, widget=forms.TextInput(attrs={'class': 'border-input'}))
	comment = forms.CharField(required=False, widget=forms.Textarea(attrs={'class': 'border-input'}))
	class Meta:
		model = Event
		fields = ['location', 'date_time', 'comment']

View:

Create an event and its participants (which are generated from existing members
def event_add(request, roster_id):

	roster = get_object_or_404(Roster, pk=roster_id)

	if request.method == 'GET':
		form=EventForm(initial={"location": roster.location})
		return render(request, 'event_edit.html',{ 'form': form, 'roster': roster})

	else:
		form = EventForm(request.POST)
		if form.is_valid():
			event = form.save()
			event.roster = roster # not user-entered
			event.save()

			# Add participants
			for member in roster.members.all():
				participant = Participant(member=member, event=event, status="available",comment=member.comment)
				participant.save()

		else: 
			print("ERROR: ", form.errors)

		return redirect("event", roster.slug, event.id)

You have defined a One-to-one relationship between Member and Participant.

This means that each Member will have one (and only one) Participant instance related to it.

Or, to phrase it a different way, you will never have more than one instance of a Participant for any individual Member.

Also, it appears to me that you have a mismatch between the models you are showing here and the code in the view. Can you confirm that you’re showing the correct code here?

Beyond these general note, can you be more specific about the issue that you’re facing?

Can you be more specific about the issue that you’re facing?

Yes. Say there is a Roster with three Members. When I create a new Event for that Roster, I also loop through the three Members to create three Participants for that Event. That works fine; the Event view shows the three Participants related to the Event. If I then create a second Event, that works fine too – except now the first event has no participants.

You have defined a One-to-one relationship between Member and Participant.

You’re right. Missed that, and I see why it’s doing that now. Switching to ForeignKey doesn’t work since there’s another ForeignKey in that model, and CompositePrimaryKeys don’t play well with admin. Looks like database design changes are needed.

I don’t see why this would be an issue.

If a Member can be a Participant in multiple Events, then Participant is the join table of a many-to-many relationship between Member and Event. (You might even make some things easier by actually defining it that way.)

Why would that be necessary here?

Why would that be necessary here?

Because with:

member = models.ForeignKey(Member, on_delete=models.CASCADE)
event = models.ForeignKey(Event, on_delete=models.CASCADE, null=False, related_name="participants")

I get (on migrate):

django.db.utils.ProgrammingError: multiple primary keys for table "dropshot_participant" are not allowed

If a Member can be a Participant in multiple Events, then Participant is the join table of a many-to-many relationship between Member and Event. (You might even make some things easier by actually defining it that way.)

That sounds plausible… but how would that be defined?

Why would that be necessary here?

As I understand it, CompositePrimaryKey was the solution to the prohibition on multiple primary keys.

This is not a problem with having two foreign keys in your model.

Having two foreign keys in a model is precisely how a many-to-many relationship is defined.

This problem is caused by trying to migrate directly from a OneToOneField defined as a primary key to a ForeignKey that is not a primary key. (Caused by the order of operations created by a migration of this type.)

You can work around this by creating two migrations before applying them.

First, create a migration to remove the member field and run that migration. Then, create another migration to create member as a ForeignKey field without the primary_key attribute.
This will work the easiest with a fresh database. Otherwise, makemigrations is going to make you supply a default value for member when inserting it. Either way, you will need to fix those references after you’re done.

See

The problem was the re-application of existing migrations. I deleted them, created a new db (nothing but test data in there, so…) and it’s now working. Thanks for the assistance.

Harry

This is not an accurate conclusion. Existing (and previously applied) migrations are not “reapplied”.

The root issue is as I described. You’re starting from a model with a OneToOneField and is your primary key, and are migrating it to a state where that field is a ForeignKey field and not a primary key. (You can see why the error is created by looking at the migration created for this conversion and the sequence in which the changes are applied.)

The existance of other / prior migrations does not factor into this at all.

I’m not following. If I update the model so that both Member and Event are ForeignKeys, create a new db, and get rid of all prior migrations, then how does the previous OneToOneField factor into it?

It doesn’t. That’s not the statement I’m correcting.

You said:

This is not correct. The problem is not “the re-application of existing migrations”, because prior migrations are not reapplied. They are applied once.
The problem was that the current state of the database had a primary key OneToOneField being migrated to a non-primary key ForeignKey field.

By deleting all existing migrations, you avoided the creation of that state, and so your new migrations worked. Absolutely no problem with that.

But making the assumption that that is something you needed to do to make it work is not correct. Having those prior migrations did not cause the problem.

So yes, you solved the problem - in what was the easiest way to do it. But you should be aware that it wasn’t necessary to go that route. If you are ever in this type of situation in the future, and it’s not convenient to just wipe the slate clean and start again, you do have alternatives.

Thanks, Ken, makes sense. Deleting the db wasn’t a problem for me at this stage, fortunately.