We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Source.number_of_melodies is not updating properly
The `number_of_melodies` of a source should correspond to the number of chants in the source that contain a volpiano entry. When I check my test sources on the database that do not have any chants that contain a volpaino, the `number_of_melodies` field matches the total number of chants. I suspect the `update_source_melody_count()` function in `signals.py` is not working as expected.

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `django/cantusdb_project/main_app/signals.py`
Content:
```
1 import operator
2 from functools import reduce
3 
4 from django.contrib.postgres.search import SearchVector
5 from django.db import models
6 from django.db.models import Value
7 from django.db.models.signals import post_save, post_delete
8 from django.dispatch import receiver
9 
10 import re
11 
12 from main_app.models import Chant
13 from main_app.models import Sequence
14 from main_app.models import Feast
15 from main_app.models import Source
16 
17 
18 @receiver(post_save, sender=Chant)
19 def on_chant_save(instance, **kwargs):
20     update_source_chant_count(instance)
21     update_source_melody_count(instance)
22 
23     update_chant_search_vector(instance)
24     update_volpiano_fields(instance)
25 
26 
27 @receiver(post_delete, sender=Chant)
28 def on_chant_delete(instance, **kwargs):
29     update_source_chant_count(instance)
30     update_source_melody_count(instance)
31 
32 
33 @receiver(post_save, sender=Sequence)
34 def on_sequence_save(instance, **kwargs):
35     update_source_chant_count(instance)
36 
37 
38 @receiver(post_delete, sender=Sequence)
39 def on_sequence_delete(instance, **kwargs):
40     update_source_chant_count(instance)
41 
42 
43 @receiver(post_save, sender=Feast)
44 def on_feast_save(instance, **kwargs):
45     update_prefix_field(instance)
46 
47 
48 def update_chant_search_vector(instance):
49     """When saving an instance of Chant, update its search vector field.
50 
51     Called in on_chant_save()
52     """
53     index_components = instance.index_components()
54     pk = instance.pk
55     search_vectors = []
56 
57     for weight, data in index_components.items():
58         search_vectors.append(
59             SearchVector(Value(data, output_field=models.TextField()), weight=weight)
60         )
61     instance.__class__.objects.filter(pk=pk).update(
62         search_vector=reduce(operator.add, search_vectors)
63     )
64 
65 
66 def update_source_chant_count(instance):
67     """When saving or deleting a Chant or Sequence, update its Source's number_of_chants field
68 
69     Called in on_chant_save(), on_chant_delete(), on_sequence_save() and on_sequence_delete()
70     """
71 
72     # When a source is deleted (which in turn calls on_chant_delete() on all of its chants) instance.source does not exist
73     try:
74         source = instance.source
75     except Source.DoesNotExist:
76         source = None
77     if source is not None:
78         source.number_of_chants = source.chant_set.count() + source.sequence_set.count()
79         source.save()
80 
81 
82 def update_source_melody_count(instance):
83     """When saving or deleting a Chant, update its Source's number_of_melodies field
84 
85     Called in on_chant_save() and on_chant_delete()
86     """
87 
88     # When a source is deleted (which in turn calls on_chant_delete() on all of its chants) instance.source does not exist
89     try:
90         source = instance.source
91     except Source.DoesNotExist:
92         source = None
93     if source is not None:
94         source.number_of_melodies = source.chant_set.filter(
95             volpiano__isnull=False
96         ).count()
97         source.save()
98 
99 
100 def update_volpiano_fields(instance):
101     """When saving a Chant, make sure the chant's volpiano_notes and volpiano_intervals are up-to-date
102 
103     Called in on_chant_save()
104     """
105 
106     def generate_volpiano_notes(volpiano):
107         """
108         Populate the ``volpiano_notes`` field of the ``Chant`` model
109 
110         This field is used for melody search
111 
112         Args:
113             volpiano (str): The content of ``chant.volpiano``
114 
115         Returns:
116             str: Volpiano str with non-note chars and duplicate consecutive notes removed
117         """
118         # unwanted_chars are non-note chars, including the clefs, barlines, and accidentals etc.
119         # the `searchMelody.js` on old cantus makes no reference to the b-flat accidentals ("y", "i", "z")
120         # so put them in unwanted chars for now
121         unwanted_chars = [
122             "-",
123             "1",
124             "2",
125             "3",
126             "4",
127             "5",
128             "6",
129             "7",
130             "?",
131             ".",
132             " ",
133             "y",
134             "i",
135             "z",
136         ]
137         # convert all charactors to lower-case, upper-case letters stand for liquescent of the same pitch
138         volpiano_lower = volpiano.lower()
139         # `)` stands for the lowest `g` note liquescent in volpiano, its 'lower case' is `9`
140         volpiano_notes = volpiano_lower.replace(")", "9")
141         # remove none-note charactors
142         for unwanted_char in unwanted_chars:
143             volpiano_notes = volpiano_notes.replace(unwanted_char, "")
144         # remove duplicate consecutive chars
145         volpiano_notes = re.sub(r"(.)\1+", r"\1", volpiano_notes)
146         return volpiano_notes
147 
148     def generate_volpiano_intervals(volpiano_notes):
149         """
150         Populate the ``volpiano_intervals`` field of the ``Chant`` model
151 
152         This field is used for melody search when searching for transpositions
153 
154         Args:
155             volpiano_notes (str): The content of ``chant.volpiano_notes``,
156             populated by the ``generate_volpiano_notes`` function
157 
158         Returns:
159             str: A str of digits, recording the intervals between adjacent notes
160         """
161         # replace '9' (the note G) with the char corresponding to (ASCII(a) - 1), because 'a' denotes the note A
162         volpiano_notes = volpiano_notes.replace("9", chr(ord("a") - 1))
163         # we model the interval between notes using the difference between the ASCII codes of corresponding letters
164         # the letter for the note B is "j" (106), note A is "h" (104), the letter "i" (105) is skipped
165         # move all notes above A down by one letter
166         volpiano_notes = list(volpiano_notes)
167         for j, note in enumerate(volpiano_notes):
168             if ord(note) >= 106:
169                 volpiano_notes[j] = chr(ord(note) - 1)
170 
171         # `intervals` records the difference between two adjacent notes.
172         # Note that intervals are encoded by counting the number of scale
173         # steps between adjacent notes: an ascending second is thus encoded
174         # as "1"; a descending third is encoded "-2", and so on.
175         intervals = []
176         for j in range(1, len(volpiano_notes)):
177             intervals.append(ord(volpiano_notes[j]) - ord(volpiano_notes[j - 1]))
178         # convert `intervals` to str
179         volpiano_intervals = "".join([str(interval) for interval in intervals])
180         return volpiano_intervals
181 
182     if instance.volpiano is None:
183         return
184 
185     volpiano_notes = generate_volpiano_notes(instance.volpiano)
186     volpiano_intervals = generate_volpiano_intervals(volpiano_notes)
187 
188     Chant.objects.filter(id=instance.id).update(
189         volpiano_notes=volpiano_notes,
190         volpiano_intervals=volpiano_intervals,
191     )
192 
193 
194 def update_prefix_field(instance):
195     pk = instance.pk
196 
197     if instance.feast_code:
198         prefix = str(instance.feast_code)[0:2]
199         instance.__class__.objects.filter(pk=pk).update(prefix=prefix)
200     else:  # feast_code is None, ""
201         instance.__class__.objects.filter(pk=pk).update(prefix="")
202
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/django/cantusdb_project/main_app/signals.py b/django/cantusdb_project/main_app/signals.py
--- a/django/cantusdb_project/main_app/signals.py
+++ b/django/cantusdb_project/main_app/signals.py
@@ -91,9 +91,11 @@
     except Source.DoesNotExist:
         source = None
     if source is not None:
-        source.number_of_melodies = source.chant_set.filter(
-            volpiano__isnull=False
-        ).count()
+        source.number_of_melodies = (
+            source.chant_set.exclude(volpiano__isnull=True)
+            .exclude(volpiano__exact="")
+            .count()
+        )
         source.save()

{"golden_diff": "diff --git a/django/cantusdb_project/main_app/signals.py b/django/cantusdb_project/main_app/signals.py\n--- a/django/cantusdb_project/main_app/signals.py\n+++ b/django/cantusdb_project/main_app/signals.py\n@@ -91,9 +91,11 @@\n     except Source.DoesNotExist:\n         source = None\n     if source is not None:\n-        source.number_of_melodies = source.chant_set.filter(\n-            volpiano__isnull=False\n-        ).count()\n+        source.number_of_melodies = (\n+            source.chant_set.exclude(volpiano__isnull=True)\n+            .exclude(volpiano__exact=\"\")\n+            .count()\n+        )\n         source.save()\n", "issue": "Source.number_of_melodies is not updating properly\nThe `number_of_melodies` of a source should correspond to the number of chants in the source that contain a volpiano entry. When I check my test sources on the database that do not have any chants that contain a volpaino, the `number_of_melodies` field matches the total number of chants. I suspect the `update_source_melody_count()` function in `signals.py` is not working as expected.\n", "before_files": [{"content": "import operator\nfrom functools import reduce\n\nfrom django.contrib.postgres.search import SearchVector\nfrom django.db import models\nfrom django.db.models import Value\nfrom django.db.models.signals import post_save, post_delete\nfrom django.dispatch import receiver\n\nimport re\n\nfrom main_app.models import Chant\nfrom main_app.models import Sequence\nfrom main_app.models import Feast\nfrom main_app.models import Source\n\n\n@receiver(post_save, sender=Chant)\ndef on_chant_save(instance, **kwargs):\n    update_source_chant_count(instance)\n    update_source_melody_count(instance)\n\n    update_chant_search_vector(instance)\n    update_volpiano_fields(instance)\n\n\n@receiver(post_delete, sender=Chant)\ndef on_chant_delete(instance, **kwargs):\n    update_source_chant_count(instance)\n    update_source_melody_count(instance)\n\n\n@receiver(post_save, sender=Sequence)\ndef on_sequence_save(instance, **kwargs):\n    update_source_chant_count(instance)\n\n\n@receiver(post_delete, sender=Sequence)\ndef on_sequence_delete(instance, **kwargs):\n    update_source_chant_count(instance)\n\n\n@receiver(post_save, sender=Feast)\ndef on_feast_save(instance, **kwargs):\n    update_prefix_field(instance)\n\n\ndef update_chant_search_vector(instance):\n    \"\"\"When saving an instance of Chant, update its search vector field.\n\n    Called in on_chant_save()\n    \"\"\"\n    index_components = instance.index_components()\n    pk = instance.pk\n    search_vectors = []\n\n    for weight, data in index_components.items():\n        search_vectors.append(\n            SearchVector(Value(data, output_field=models.TextField()), weight=weight)\n        )\n    instance.__class__.objects.filter(pk=pk).update(\n        search_vector=reduce(operator.add, search_vectors)\n    )\n\n\ndef update_source_chant_count(instance):\n    \"\"\"When saving or deleting a Chant or Sequence, update its Source's number_of_chants field\n\n    Called in on_chant_save(), on_chant_delete(), on_sequence_save() and on_sequence_delete()\n    \"\"\"\n\n    # When a source is deleted (which in turn calls on_chant_delete() on all of its chants) instance.source does not exist\n    try:\n        source = instance.source\n    except Source.DoesNotExist:\n        source = None\n    if source is not None:\n        source.number_of_chants = source.chant_set.count() + source.sequence_set.count()\n        source.save()\n\n\ndef update_source_melody_count(instance):\n    \"\"\"When saving or deleting a Chant, update its Source's number_of_melodies field\n\n    Called in on_chant_save() and on_chant_delete()\n    \"\"\"\n\n    # When a source is deleted (which in turn calls on_chant_delete() on all of its chants) instance.source does not exist\n    try:\n        source = instance.source\n    except Source.DoesNotExist:\n        source = None\n    if source is not None:\n        source.number_of_melodies = source.chant_set.filter(\n            volpiano__isnull=False\n        ).count()\n        source.save()\n\n\ndef update_volpiano_fields(instance):\n    \"\"\"When saving a Chant, make sure the chant's volpiano_notes and volpiano_intervals are up-to-date\n\n    Called in on_chant_save()\n    \"\"\"\n\n    def generate_volpiano_notes(volpiano):\n        \"\"\"\n        Populate the ``volpiano_notes`` field of the ``Chant`` model\n\n        This field is used for melody search\n\n        Args:\n            volpiano (str): The content of ``chant.volpiano``\n\n        Returns:\n            str: Volpiano str with non-note chars and duplicate consecutive notes removed\n        \"\"\"\n        # unwanted_chars are non-note chars, including the clefs, barlines, and accidentals etc.\n        # the `searchMelody.js` on old cantus makes no reference to the b-flat accidentals (\"y\", \"i\", \"z\")\n        # so put them in unwanted chars for now\n        unwanted_chars = [\n            \"-\",\n            \"1\",\n            \"2\",\n            \"3\",\n            \"4\",\n            \"5\",\n            \"6\",\n            \"7\",\n            \"?\",\n            \".\",\n            \" \",\n            \"y\",\n            \"i\",\n            \"z\",\n        ]\n        # convert all charactors to lower-case, upper-case letters stand for liquescent of the same pitch\n        volpiano_lower = volpiano.lower()\n        # `)` stands for the lowest `g` note liquescent in volpiano, its 'lower case' is `9`\n        volpiano_notes = volpiano_lower.replace(\")\", \"9\")\n        # remove none-note charactors\n        for unwanted_char in unwanted_chars:\n            volpiano_notes = volpiano_notes.replace(unwanted_char, \"\")\n        # remove duplicate consecutive chars\n        volpiano_notes = re.sub(r\"(.)\\1+\", r\"\\1\", volpiano_notes)\n        return volpiano_notes\n\n    def generate_volpiano_intervals(volpiano_notes):\n        \"\"\"\n        Populate the ``volpiano_intervals`` field of the ``Chant`` model\n\n        This field is used for melody search when searching for transpositions\n\n        Args:\n            volpiano_notes (str): The content of ``chant.volpiano_notes``,\n            populated by the ``generate_volpiano_notes`` function\n\n        Returns:\n            str: A str of digits, recording the intervals between adjacent notes\n        \"\"\"\n        # replace '9' (the note G) with the char corresponding to (ASCII(a) - 1), because 'a' denotes the note A\n        volpiano_notes = volpiano_notes.replace(\"9\", chr(ord(\"a\") - 1))\n        # we model the interval between notes using the difference between the ASCII codes of corresponding letters\n        # the letter for the note B is \"j\" (106), note A is \"h\" (104), the letter \"i\" (105) is skipped\n        # move all notes above A down by one letter\n        volpiano_notes = list(volpiano_notes)\n        for j, note in enumerate(volpiano_notes):\n            if ord(note) >= 106:\n                volpiano_notes[j] = chr(ord(note) - 1)\n\n        # `intervals` records the difference between two adjacent notes.\n        # Note that intervals are encoded by counting the number of scale\n        # steps between adjacent notes: an ascending second is thus encoded\n        # as \"1\"; a descending third is encoded \"-2\", and so on.\n        intervals = []\n        for j in range(1, len(volpiano_notes)):\n            intervals.append(ord(volpiano_notes[j]) - ord(volpiano_notes[j - 1]))\n        # convert `intervals` to str\n        volpiano_intervals = \"\".join([str(interval) for interval in intervals])\n        return volpiano_intervals\n\n    if instance.volpiano is None:\n        return\n\n    volpiano_notes = generate_volpiano_notes(instance.volpiano)\n    volpiano_intervals = generate_volpiano_intervals(volpiano_notes)\n\n    Chant.objects.filter(id=instance.id).update(\n        volpiano_notes=volpiano_notes,\n        volpiano_intervals=volpiano_intervals,\n    )\n\n\ndef update_prefix_field(instance):\n    pk = instance.pk\n\n    if instance.feast_code:\n        prefix = str(instance.feast_code)[0:2]\n        instance.__class__.objects.filter(pk=pk).update(prefix=prefix)\n    else:  # feast_code is None, \"\"\n        instance.__class__.objects.filter(pk=pk).update(prefix=\"\")\n", "path": "django/cantusdb_project/main_app/signals.py"}], "after_files": [{"content": "import operator\nfrom functools import reduce\n\nfrom django.contrib.postgres.search import SearchVector\nfrom django.db import models\nfrom django.db.models import Value\nfrom django.db.models.signals import post_save, post_delete\nfrom django.dispatch import receiver\n\nimport re\n\nfrom main_app.models import Chant\nfrom main_app.models import Sequence\nfrom main_app.models import Feast\nfrom main_app.models import Source\n\n\n@receiver(post_save, sender=Chant)\ndef on_chant_save(instance, **kwargs):\n    update_source_chant_count(instance)\n    update_source_melody_count(instance)\n\n    update_chant_search_vector(instance)\n    update_volpiano_fields(instance)\n\n\n@receiver(post_delete, sender=Chant)\ndef on_chant_delete(instance, **kwargs):\n    update_source_chant_count(instance)\n    update_source_melody_count(instance)\n\n\n@receiver(post_save, sender=Sequence)\ndef on_sequence_save(instance, **kwargs):\n    update_source_chant_count(instance)\n\n\n@receiver(post_delete, sender=Sequence)\ndef on_sequence_delete(instance, **kwargs):\n    update_source_chant_count(instance)\n\n\n@receiver(post_save, sender=Feast)\ndef on_feast_save(instance, **kwargs):\n    update_prefix_field(instance)\n\n\ndef update_chant_search_vector(instance):\n    \"\"\"When saving an instance of Chant, update its search vector field.\n\n    Called in on_chant_save()\n    \"\"\"\n    index_components = instance.index_components()\n    pk = instance.pk\n    search_vectors = []\n\n    for weight, data in index_components.items():\n        search_vectors.append(\n            SearchVector(Value(data, output_field=models.TextField()), weight=weight)\n        )\n    instance.__class__.objects.filter(pk=pk).update(\n        search_vector=reduce(operator.add, search_vectors)\n    )\n\n\ndef update_source_chant_count(instance):\n    \"\"\"When saving or deleting a Chant or Sequence, update its Source's number_of_chants field\n\n    Called in on_chant_save(), on_chant_delete(), on_sequence_save() and on_sequence_delete()\n    \"\"\"\n\n    # When a source is deleted (which in turn calls on_chant_delete() on all of its chants) instance.source does not exist\n    try:\n        source = instance.source\n    except Source.DoesNotExist:\n        source = None\n    if source is not None:\n        source.number_of_chants = source.chant_set.count() + source.sequence_set.count()\n        source.save()\n\n\ndef update_source_melody_count(instance):\n    \"\"\"When saving or deleting a Chant, update its Source's number_of_melodies field\n\n    Called in on_chant_save() and on_chant_delete()\n    \"\"\"\n\n    # When a source is deleted (which in turn calls on_chant_delete() on all of its chants) instance.source does not exist\n    try:\n        source = instance.source\n    except Source.DoesNotExist:\n        source = None\n    if source is not None:\n        source.number_of_melodies = (\n            source.chant_set.exclude(volpiano__isnull=True)\n            .exclude(volpiano__exact=\"\")\n            .count()\n        )\n        source.save()\n\n\ndef update_volpiano_fields(instance):\n    \"\"\"When saving a Chant, make sure the chant's volpiano_notes and volpiano_intervals are up-to-date\n\n    Called in on_chant_save()\n    \"\"\"\n\n    def generate_volpiano_notes(volpiano):\n        \"\"\"\n        Populate the ``volpiano_notes`` field of the ``Chant`` model\n\n        This field is used for melody search\n\n        Args:\n            volpiano (str): The content of ``chant.volpiano``\n\n        Returns:\n            str: Volpiano str with non-note chars and duplicate consecutive notes removed\n        \"\"\"\n        # unwanted_chars are non-note chars, including the clefs, barlines, and accidentals etc.\n        # the `searchMelody.js` on old cantus makes no reference to the b-flat accidentals (\"y\", \"i\", \"z\")\n        # so put them in unwanted chars for now\n        unwanted_chars = [\n            \"-\",\n            \"1\",\n            \"2\",\n            \"3\",\n            \"4\",\n            \"5\",\n            \"6\",\n            \"7\",\n            \"?\",\n            \".\",\n            \" \",\n            \"y\",\n            \"i\",\n            \"z\",\n        ]\n        # convert all charactors to lower-case, upper-case letters stand for liquescent of the same pitch\n        volpiano_lower = volpiano.lower()\n        # `)` stands for the lowest `g` note liquescent in volpiano, its 'lower case' is `9`\n        volpiano_notes = volpiano_lower.replace(\")\", \"9\")\n        # remove none-note charactors\n        for unwanted_char in unwanted_chars:\n            volpiano_notes = volpiano_notes.replace(unwanted_char, \"\")\n        # remove duplicate consecutive chars\n        volpiano_notes = re.sub(r\"(.)\\1+\", r\"\\1\", volpiano_notes)\n        return volpiano_notes\n\n    def generate_volpiano_intervals(volpiano_notes):\n        \"\"\"\n        Populate the ``volpiano_intervals`` field of the ``Chant`` model\n\n        This field is used for melody search when searching for transpositions\n\n        Args:\n            volpiano_notes (str): The content of ``chant.volpiano_notes``,\n            populated by the ``generate_volpiano_notes`` function\n\n        Returns:\n            str: A str of digits, recording the intervals between adjacent notes\n        \"\"\"\n        # replace '9' (the note G) with the char corresponding to (ASCII(a) - 1), because 'a' denotes the note A\n        volpiano_notes = volpiano_notes.replace(\"9\", chr(ord(\"a\") - 1))\n        # we model the interval between notes using the difference between the ASCII codes of corresponding letters\n        # the letter for the note B is \"j\" (106), note A is \"h\" (104), the letter \"i\" (105) is skipped\n        # move all notes above A down by one letter\n        volpiano_notes = list(volpiano_notes)\n        for j, note in enumerate(volpiano_notes):\n            if ord(note) >= 106:\n                volpiano_notes[j] = chr(ord(note) - 1)\n\n        # `intervals` records the difference between two adjacent notes.\n        # Note that intervals are encoded by counting the number of scale\n        # steps between adjacent notes: an ascending second is thus encoded\n        # as \"1\"; a descending third is encoded \"-2\", and so on.\n        intervals = []\n        for j in range(1, len(volpiano_notes)):\n            intervals.append(ord(volpiano_notes[j]) - ord(volpiano_notes[j - 1]))\n        # convert `intervals` to str\n        volpiano_intervals = \"\".join([str(interval) for interval in intervals])\n        return volpiano_intervals\n\n    if instance.volpiano is None:\n        return\n\n    volpiano_notes = generate_volpiano_notes(instance.volpiano)\n    volpiano_intervals = generate_volpiano_intervals(volpiano_notes)\n\n    Chant.objects.filter(id=instance.id).update(\n        volpiano_notes=volpiano_notes,\n        volpiano_intervals=volpiano_intervals,\n    )\n\n\ndef update_prefix_field(instance):\n    pk = instance.pk\n\n    if instance.feast_code:\n        prefix = str(instance.feast_code)[0:2]\n        instance.__class__.objects.filter(pk=pk).update(prefix=prefix)\n    else:  # feast_code is None, \"\"\n        instance.__class__.objects.filter(pk=pk).update(prefix=\"\")\n", "path": "django/cantusdb_project/main_app/signals.py"}]}